diff options
author | joye <joye> | 2013-08-27 18:44:24 (GMT) |
---|---|---|
committer | joye <joye> | 2013-08-27 18:44:24 (GMT) |
commit | 48974a09f23839821ca95f228fc0f3f53bb1cefa (patch) | |
tree | 9d5cfa1305ef57b69c9b1dd09dc3ffe536c0777d | |
parent | 05d8c009040abfcb5f62644fbb99c8ff453d4519 (diff) | |
download | blt-48974a09f23839821ca95f228fc0f3f53bb1cefa.zip blt-48974a09f23839821ca95f228fc0f3f53bb1cefa.tar.gz blt-48974a09f23839821ca95f228fc0f3f53bb1cefa.tar.bz2 |
*** empty log message ***
39 files changed, 52779 insertions, 0 deletions
diff --git a/library/graph.tcl b/library/graph.tcl new file mode 100644 index 0000000..8c4fd8d --- /dev/null +++ b/library/graph.tcl @@ -0,0 +1,842 @@ + +namespace eval ::blt::legend { + variable _private + array set _private { + afterId "" + scroll 0 + space off + drag 0 + x 0 + y 0 + } +} + +namespace eval ::blt::ZoomStack { + variable _private + array set _private { + afterId "" + scroll 0 + space off + drag 0 + x 0 + y 0 + } +} + +option add *zoomOutline.dashes 4 +option add *zoomOutline.lineWidth 2 +option add *zoomOutline.xor yes +option add *zoomTitle.anchor nw +option add *zoomTitle.coords "-Inf Inf" +option add *zoomTitle.font "Arial 14" +option add *zoomTitle.foreground yellow3 +option add *zoomTitle.shadow yellow4 + +# ---------------------------------------------------------------------- +# +# Initialize -- +# +# Invoked by internally by Treeview_Init routine. Initializes +# the default bindings for the treeview widget entries. These +# are local to the widget, so they can't be set through the +# widget's class bind tags. +# +# ---------------------------------------------------------------------- +proc blt::LegendSelections { w } { + if 0 { + # + # Active entry bindings + # + $w legend bind all <Enter> { + %W entry highlight current + } + $w legend bind all <Leave> { + %W entry highlight "" + } + } + + # + # ButtonPress-1 + # + # Performs the following operations: + # + # 1. Clears the previous selection. + # 2. Selects the current entry. + # 3. Sets the focus to this entry. + # 4. Scrolls the entry into view. + # 5. Sets the selection anchor to this entry, just in case + # this is "multiple" mode. + # + + $w legend bind all <ButtonPress-1> { + blt::legend::SetSelectionAnchor %W current + set blt::legend::_private(scroll) 1 + } + + # + # B1-Motion + # + # For "multiple" mode only. Saves the current location of the + # pointer for auto-scrolling. Resets the selection mark. + # + $w legend bind all <B1-Motion> { + set blt::legend::_private(x) %x + set blt::legend::_private(y) %y + set elem [%W legend get @%x,%y] + if { $elem != "" } { + if { [%W legend cget -selectmode] == "multiple" } { + %W legend selection mark $elem + } else { + blt::legend::SetSelectionAnchor %W $elem + } + } + } + + # + # ButtonRelease-1 + # + # For "multiple" mode only. + # + $w legend bind all <ButtonRelease-1> { + if { [%W legend cget -selectmode] == "multiple" } { + %W legend selection anchor current + } + after cancel $blt::legend::_private(afterId) + set blt::legend::_private(scroll) 0 + } + + # + # Shift-ButtonPress-1 + # + # For "multiple" mode only. + # + + $w legend bind all <Shift-ButtonPress-1> { + if { [%W legend cget -selectmode] == "multiple" && + [%W legend selection present] } { + if { [%W legend get anchor] == "" } { + %W legend selection anchor current + } + set elem [%W legend get anchor] + %W legend selection clearall + %W legend selection set $elem current + } else { + blt::legend::SetSelectionAnchor %W current + } + } + $w legend bind all <Shift-Double-ButtonPress-1> { + # do nothing + } + $w legend bind all <Shift-B1-Motion> { + # do nothing + } + $w legend bind all <Shift-ButtonRelease-1> { + after cancel $blt::legend::_private(afterId) + set blt::legend::_private(scroll) 0 + } + + # + # Control-ButtonPress-1 + # + # For "multiple" mode only. + # + $w legend bind all <Control-ButtonPress-1> { + if { [%W legend cget -selectmode] == "multiple" } { + set elem [%W legend get current] + %W legend selection toggle $elem + %W legend selection anchor $elem + } else { + blt::legend::SetSelectionAnchor %W current + } + } + $w legend bind all <Control-Double-ButtonPress-1> { + # do nothing + } + $w legend bind all <Control-B1-Motion> { + # do nothing + } + $w legend bind all <Control-ButtonRelease-1> { + after cancel $blt::legend::_private(afterId) + set blt::legend::_private(scroll) 0 + } + + $w legend bind all <Control-Shift-ButtonPress-1> { + if { [%W legend cget -selectmode] == "multiple" && + [%W legend selection present] } { + if { [%W legend get anchor] == "" } { + %W selection anchor current + } + if { [%W legend selection includes anchor] } { + %W legend selection set anchor current + } else { + %W legend selection clear anchor current + %W legend selection set current + } + } else { + blt::legend::SetSelectionAnchor %W current + } + } + $w legend bind all <Control-Shift-Double-ButtonPress-1> { + # do nothing + } + $w legend bind all <Control-Shift-B1-Motion> { + # do nothing + } + $w legend bind all <KeyPress-Up> { + blt::legend::MoveFocus %W previous.row + if { $blt::legend::_private(space) } { + %W legend selection toggle focus + } + } + $w legend bind all <KeyPress-Down> { + blt::legend::MoveFocus %W next.row + if { $blt::legend::_private(space) } { + %W legend selection toggle focus + } + } + $w legend bind all <KeyPress-Left> { + blt::legend::MoveFocus %W previous.column + if { $blt::legend::_private(space) } { + %W legend selection toggle focus + } + } + $w legend bind all <KeyPress-Right> { + blt::legend::MoveFocus %W next.column + if { $blt::legend::_private(space) } { + %W legend selection toggle focus + } + } + $w legend bind all <KeyPress-space> { + if { [%W legend cget -selectmode] == "single" } { + if { [%W legend selection includes focus] } { + %W legend selection clearall + } else { + %W legend selection clearall + %W legend selection set focus + } + } else { + %W legend selection toggle focus + } + set blt::legend::_private(space) on + } + + $w legend bind all <KeyRelease-space> { + set blt::legend::_private(space) off + } + $w legend bind all <KeyPress-Return> { + blt::legend::MoveFocus %W focus + set blt::legend::_private(space) on + } + $w legend bind all <KeyRelease-Return> { + set blt::legend::_private(space) off + } + $w legend bind all <KeyPress-Home> { + blt::legend::MoveFocus %W first + } + $w legend bind all <KeyPress-End> { + blt::tv::MoveFocus %W last + } +} + +proc blt::legend::SetSelectionAnchor { w tagOrId } { + set elem [$w legend get $tagOrId] + # If the anchor hasn't changed, don't do anything + if { $elem != [$w legend get anchor] } { + $w legend selection clearall + $w legend focus $elem + $w legend selection set $elem + $w legend selection anchor $elem + } +} + +# ---------------------------------------------------------------------- +# +# MoveFocus -- +# +# Invoked by KeyPress bindings. Moves the active selection to +# the entry <where>, which is an index such as "up", "down", +# "prevsibling", "nextsibling", etc. +# +# ---------------------------------------------------------------------- +proc blt::legend::MoveFocus { w elem } { + catch {$w legend focus $elem} result + puts stderr "result=$result elem=$elem" + if { [$w legend cget -selectmode] == "single" } { + $w legend selection clearall + $w legend selection set focus + $w legend selection anchor focus + } +} + + +proc Blt_ActiveLegend { g } { + $g legend bind all <Enter> [list blt::ActivateLegend $g ] + $g legend bind all <Leave> [list blt::DeactivateLegend $g] + $g legend bind all <ButtonPress-1> [list blt::HighlightLegend $g] +} + +proc Blt_Crosshairs { g } { + blt::Crosshairs $g +} + +proc Blt_ResetCrosshairs { g state } { + blt::Crosshairs $g "Any-Motion" $state +} + +proc Blt_ZoomStack { g args } { + array set params { + -mode click + } + array set params $args + if { $params(-mode) == "click" } { + blt::ZoomStack::ClickClick $g + } else { + blt::ZoomStack::ClickRelease $g + } +} + +proc Blt_PrintKey { g } { + blt::PrintKey $g +} + +proc Blt_ClosestPoint { g } { + blt::ClosestPoint $g +} + +# +# The following procedures that reside in the "blt" namespace are +# supposed to be private. +# + +proc blt::ActivateLegend { g } { + set elem [$g legend get current] + $g legend activate $elem +} +proc blt::DeactivateLegend { g } { + set elem [$g legend get current] + $g legend deactivate $elem +} + +proc blt::HighlightLegend { g } { + set elem [$g legend get current] + if { $elem != "" } { + set relief [$g element cget $elem -legendrelief] + if { $relief == "flat" } { + $g element configure $elem -legendrelief raised + $g element activate $elem + } else { + $g element configure $elem -legendrelief flat + $g element deactivate $elem + } + } +} + +proc blt::Crosshairs { g {event "Any-Motion"} {state "on"}} { + $g crosshairs $state + bind crosshairs-$g <$event> { + %W crosshairs configure -position @%x,%y + } + bind crosshairs-$g <Leave> { + %W crosshairs off + } + bind crosshairs-$g <Enter> { + %W crosshairs on + } + $g crosshairs configure -color red + if { $state == "on" } { + blt::AddBindTag $g crosshairs-$g + } elseif { $state == "off" } { + blt::RemoveBindTag $g crosshairs-$g + } +} + +proc blt::PrintKey { g {event "Shift-ButtonRelease-3"} } { + bind print-$g <$event> { Blt_PostScriptDialog %W } + blt::AddBindTag $g print-$g +} + +proc blt::ClosestPoint { g {event "Control-ButtonPress-2"} } { + bind closest-point-$g <$event> { + blt::FindElement %W %x %y + } + blt::AddBindTag $g closest-point-$g +} + +proc blt::AddBindTag { widget tag } { + set oldTagList [bindtags $widget] + if { [lsearch $oldTagList $tag] < 0 } { + bindtags $widget [linsert $oldTagList 0 $tag] + } +} + +proc blt::RemoveBindTag { widget tag } { + set oldTagList [bindtags $widget] + set index [lsearch $oldTagList $tag] + if { $index >= 0 } { + bindtags $widget [lreplace $oldTagList $index $index] + } +} + +proc blt::FindElement { g x y } { + array set info [$g element closest $x $y -interpolate yes] + if { ![info exists info(name)] } { + beep + return + } + # -------------------------------------------------------------- + # find(name) - element Id + # find(index) - index of closest point + # find(x) find(y) - coordinates of closest point + # or closest point on line segment. + # find(dist) - distance from sample coordinate + # -------------------------------------------------------------- + set markerName "bltClosest_$info(name)" + catch { $g marker delete $markerName } + $g marker create text -coords { $info(x) $info(y) } \ + -name $markerName \ + -text "$info(name): $info(dist)\nindex $info(index)" \ + -font "Arial 6" \ + -anchor center -justify left \ + -yoffset 0 -bg {} + + set coords [$g invtransform $x $y] + set nx [lindex $coords 0] + set ny [lindex $coords 1] + + $g marker create line -coords "$nx $ny $info(x) $info(y)" \ + -name line.$markerName + + blt::FlashPoint $g $info(name) $info(index) 10 + blt::FlashPoint $g $info(name) [expr $info(index) + 1] 10 +} + +proc blt::FlashPoint { g name index count } { + if { $count & 1 } { + $g element deactivate $name + } else { + $g element activate $name $index + } + incr count -1 + if { $count > 0 } { + after 200 blt::FlashPoint $g $name $index $count + update + } else { + eval $g marker delete [$g marker names "bltClosest_*"] + } +} + + +proc blt::ZoomStack::Init { g } { + variable _private + set _private($g,interval) 100 + set _private($g,afterId) 0 + set _private($g,A,x) {} + set _private($g,A,y) {} + set _private($g,B,x) {} + set _private($g,B,y) {} + set _private($g,stack) {} + set _private($g,corner) A +} + +proc blt::ZoomStack::ClickClick { g {start "ButtonPress-1"} {reset "ButtonPress-3"} } { + variable _private + + Init $g + + bind zoom-$g <Enter> "focus %W" + bind zoom-$g <KeyPress-Escape> { blt::ZoomStack::Reset %W } + bind zoom-$g <${start}> { blt::ZoomStack::SetPoint %W %x %y } + bind zoom-$g <${reset}> { + if { [%W inside %x %y] } { + blt::ZoomStack::Reset %W + } + } + blt::AddBindTag $g zoom-$g +} + +proc blt::ZoomStack::ClickRelease { g } { + variable _private + + Init $g + bind zoom-$g <Enter> "focus %W" + bind zoom-$g <KeyPress-Escape> { blt::ZoomStack::Reset %W } + bind zoom-$g <ButtonPress-1> { blt::ZoomStack::DragStart %W %x %y } + bind zoom-$g <B1-Motion> { blt::ZoomStack::DragMotion %W %x %y } + bind zoom-$g <ButtonRelease-1> { blt::ZoomStack::DragFinish %W %x %y } + bind zoom-$g <ButtonPress-3> { + if { [%W inside %x %y] } { + blt::ZoomStack::Reset %W + } + } + blt::AddBindTag $g zoom-$g +} + +proc blt::ZoomStack::GetCoords { g x y index } { + variable _private + if { [$g cget -invertxy] } { + set _private($g,$index,x) $y + set _private($g,$index,y) $x + } else { + set _private($g,$index,x) $x + set _private($g,$index,y) $y + } +} + +proc blt::ZoomStack::MarkPoint { g index } { + variable _private + + if { [llength [$g xaxis use]] > 0 } { + set x [$g xaxis invtransform $_private($g,$index,x)] + } else if { [llength [$g x2axis use]] > 0 } { + set x [$g x2axis invtransform $_private($g,$index,x)] + } + if { [llength [$g yaxis use]] > 0 } { + set y [$g yaxis invtransform $_private($g,$index,y)] + } else if { [llength [$g y2axis use]] > 0 } { + set y [$g y2axis invtransform $_private($g,$index,y)] + } + set marker "zoomText_$index" + set text [format "x=%.4g\ny=%.4g" $x $y] + + if [$g marker exists $marker] { + $g marker configure $marker -coords { $x $y } -text $text + } else { + $g marker create text -coords { $x $y } -name $marker \ + -font "mathmatica1 10" \ + -text $text -anchor center -bg {} -justify left + } +} + +proc blt::ZoomStack::DestroyTitle { g } { + variable _private + + if { $_private($g,corner) == "A" } { + catch { $g marker delete "zoomTitle" } + } +} + +proc blt::ZoomStack::Pop { g } { + variable _private + + set zoomStack $_private($g,stack) + if { [llength $zoomStack] > 0 } { + set cmd [lindex $zoomStack 0] + set _private($g,stack) [lrange $zoomStack 1 end] + eval $cmd + TitleLast $g + blt::busy hold $g + update + blt::busy release $g + after 2000 [list blt::ZoomStack::DestroyTitle $g] + } else { + catch { $g marker delete "zoomTitle" } + } +} + +# Push the old axis limits on the stack and set the new ones + +proc blt::ZoomStack::Push { g } { + variable _private + + eval $g marker delete [$g marker names "zoom*"] + if { [info exists _private($g,afterId)] } { + after cancel $_private($g,afterId) + } + set x1 $_private($g,A,x) + set y1 $_private($g,A,y) + set x2 $_private($g,B,x) + set y2 $_private($g,B,y) + + if { ($x1 == $x2) || ($y1 == $y2) } { + # No delta, revert to start + return + } + set cmd {} + foreach axis [$g axis names] { + if { [$g axis cget $axis -hide] } { + continue + } + set min [$g axis cget $axis -min] + set max [$g axis cget $axis -max] + set logscale [$g axis cget $axis -logscale] + # Save the current scale (log or linear) so that we can restore it. + # This is for the case where the user changes to logscale while + # zooming. A previously pushed axis limit could be negative. It + # seems better for popping the zoom stack to restore a previous view + # (not convert the ranges). + set c [list $g axis configure $axis] + lappend c -min $min -max $max -logscale $logscale + append cmd "$c\n" + } + + # This effectively pushes the command to reset the graph to the current + # zoom level onto the stack. This is useful if the new axis ranges are + # bad and we need to reset the zoom stack. + set _private($g,stack) [linsert $_private($g,stack) 0 $cmd] + foreach axis [$g axis names] { + if { [$g axis cget $axis -hide] } { + continue; # Don't set zoom on axes not displayed. + } + set type [$g axis type $axis] + if { $type == "x" } { + set min [$g axis invtransform $axis $x1] + set max [$g axis invtransform $axis $x2] + } elseif { $type == "y" } { + set min [$g axis invtransform $axis $y1] + set max [$g axis invtransform $axis $y2] + } else { + continue; # Axis is not bound to any margin. + } + if { ![SetAxisRanges $g $axis $min $max] } { + Pop $g + bell + return + } + } + blt::busy hold $g + update; # This "update" redraws the graph + blt::busy release $g +} + +proc blt::ZoomStack::SetAxisRanges { g axis min max } { + if { $min > $max } { + set tmp $max; set max $min; set min $tmp + } + if { [catch { $g axis configure $axis -min $min -max $max }] != 0 } { + return 0 + } + return 1 +} + +# +# This routine terminates either an existing zoom, or pops back to +# the previous zoom level (if no zoom is in progress). +# +proc blt::ZoomStack::Reset { g } { + variable _private + + if { ![info exists _private($g,corner)] } { + Init $g + } + eval $g marker delete [$g marker names "zoom*"] + + if { $_private($g,corner) == "A" } { + # Reset the whole axis + Pop $g + } else { + set _private($g,corner) A + blt::RemoveBindTag $g select-region-$g + } +} + +proc blt::ZoomStack::TitleNext { g } { + variable _private + + set level [expr [llength $_private($g,stack)] + 1] + if { [$g cget -invertxy] } { + set coords "Inf -Inf" + } else { + set coords "-Inf Inf" + } + $g marker create text -name "zoomTitle" -text "Zoom #$level" \ + -coords $coords -bindtags "" -anchor nw +} + +proc blt::ZoomStack::TitleLast { g } { + variable _private + + set level [llength $_private($g,stack)] + if { $level > 0 } { + $g marker create text -name "zoomTitle" -anchor nw \ + -text "Zoom #$level" + } +} + + +proc blt::ZoomStack::SetPoint { g x y } { + variable _private + if { ![info exists _private($g,corner)] } { + Init $g + } + GetCoords $g $x $y $_private($g,corner) + bind select-region-$g <Motion> { + blt::ZoomStack::GetCoords %W %x %y B + #blt::ZoomStack::MarkPoint $g B + blt::ZoomStack::Box %W + } + if { $_private($g,corner) == "A" } { + if { ![$g inside $x $y] } { + return + } + # First corner selected, start watching motion events + + #MarkPoint $g A + TitleNext $g + + blt::AddBindTag $g select-region-$g + set _private($g,corner) B + } else { + # Delete the modal binding + blt::RemoveBindTag $g select-region-$g + Push $g + set _private($g,corner) A + } +} + +proc blt::ZoomStack::DragStart { g x y } { + variable _private + if { ![info exists _private($g,corner)] } { + Init $g + } + GetCoords $g $x $y A + if { ![$g inside $x $y] } { + return + } + set _private(drag) 1 + TitleNext $g +} + +proc blt::ZoomStack::DragMotion { g x y } { + variable _private + + if { $_private(drag) } { + GetCoords $g $x $y B + set dx [expr abs($_private($g,B,x) - $_private($g,A,x))] + set dy [expr abs($_private($g,B,y) - $_private($g,A,y))] + Box $g + if { $dy > 10 && $dx > 10 } { + return 1 + } + } + return 0 +} + +proc blt::ZoomStack::DragFinish { g x y } { + variable _private + if { [DragMotion $g $x $y] } { + Push $g + } else { + eval $g marker delete [$g marker names "zoom*"] + if { [info exists _private($g,afterId)] } { + after cancel $_private($g,afterId) + } + } + set _private(drag) 0 +} + + +proc blt::ZoomStack::MarchingAnts { g offset } { + variable _private + + incr offset + # wrap the counter after 2^16 + set offset [expr $offset & 0xFFFF] + if { [$g marker exists zoomOutline] } { + $g marker configure zoomOutline -dashoffset $offset + set interval $_private($g,interval) + set id [after $interval [list blt::ZoomStack::MarchingAnts $g $offset]] + set _private($g,afterId) $id + } +} + +proc blt::ZoomStack::Box { g } { + variable _private + + if { $_private($g,A,x) > $_private($g,B,x) } { + set x1 [$g xaxis invtransform $_private($g,B,x)] + set y1 [$g yaxis invtransform $_private($g,B,y)] + set x2 [$g xaxis invtransform $_private($g,A,x)] + set y2 [$g yaxis invtransform $_private($g,A,y)] + } else { + set x1 [$g xaxis invtransform $_private($g,A,x)] + set y1 [$g yaxis invtransform $_private($g,A,y)] + set x2 [$g xaxis invtransform $_private($g,B,x)] + set y2 [$g yaxis invtransform $_private($g,B,y)] + } + set coords { $x1 $y1 $x2 $y1 $x2 $y2 $x1 $y2 $x1 $y1 } + if { [$g marker exists "zoomOutline"] } { + $g marker configure "zoomOutline" -coords $coords + } else { + set X [lindex [$g xaxis use] 0] + set Y [lindex [$g yaxis use] 0] + $g marker create line -coords $coords -name "zoomOutline" \ + -mapx $X -mapy $Y + set interval $_private($g,interval) + set id [after $interval [list blt::ZoomStack::MarchingAnts $g 0]] + set _private($g,afterId) $id + } +} + + +proc Blt_PostScriptDialog { g } { + set top $g.top + toplevel $top + + foreach var { center landscape maxpect preview decorations padx + pady paperwidth paperheight width height colormode } { + global $g.$var + set $g.$var [$g postscript cget -$var] + } + set row 1 + set col 0 + label $top.title -text "PostScript Options" + blt::table $top $top.title -cspan 7 + foreach bool { center landscape maxpect preview decorations } { + set w $top.$bool-label + label $w -text "-$bool" -font "courier 12" + blt::table $top $row,$col $w -anchor e -pady { 2 0 } -padx { 0 4 } + set w $top.$bool-yes + global $g.$bool + radiobutton $w -text "yes" -variable $g.$bool -value 1 + blt::table $top $row,$col+1 $w -anchor w + set w $top.$bool-no + radiobutton $w -text "no" -variable $g.$bool -value 0 + blt::table $top $row,$col+2 $w -anchor w + incr row + } + label $top.modes -text "-colormode" -font "courier 12" + blt::table $top $row,0 $top.modes -anchor e -pady { 2 0 } -padx { 0 4 } + set col 1 + foreach m { color greyscale } { + set w $top.$m + radiobutton $w -text $m -variable $g.colormode -value $m + blt::table $top $row,$col $w -anchor w + incr col + } + set row 1 + frame $top.sep -width 2 -bd 1 -relief sunken + blt::table $top $row,3 $top.sep -fill y -rspan 6 + set col 4 + foreach value { padx pady paperwidth paperheight width height } { + set w $top.$value-label + label $w -text "-$value" -font "courier 12" + blt::table $top $row,$col $w -anchor e -pady { 2 0 } -padx { 0 4 } + set w $top.$value-entry + global $g.$value + entry $w -textvariable $g.$value -width 8 + blt::table $top $row,$col+1 $w -cspan 2 -anchor w -padx 8 + incr row + } + blt::table configure $top c3 -width .125i + button $top.cancel -text "Cancel" -command "destroy $top" + blt::table $top $row,0 $top.cancel -width 1i -pady 2 -cspan 3 + button $top.reset -text "Reset" -command "destroy $top" + #blt::table $top $row,1 $top.reset -width 1i + button $top.print -text "Print" -command "blt::ResetPostScript $g" + blt::table $top $row,4 $top.print -width 1i -pady 2 -cspan 2 +} + +proc blt::ResetPostScript { g } { + foreach var { center landscape maxpect preview decorations padx + pady paperwidth paperheight width height colormode } { + global $g.$var + set old [$g postscript cget -$var] + if { [catch {$g postscript configure -$var [set $g.$var]}] != 0 } { + $g postscript configure -$var $old + set $g.$var $old + } + } + $g postscript output "out.ps" + puts stdout "wrote file \"out.ps\"." + flush stdout +} diff --git a/src/bltChain.C b/src/bltChain.C new file mode 100644 index 0000000..b6051b8 --- /dev/null +++ b/src/bltChain.C @@ -0,0 +1,518 @@ + +/* + * bltChain.c -- + * + * The module implements a generic linked list package. + * + * Copyright 1991-2004 George A Howlett. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include "bltInt.h" +#include "bltChain.h" + +#ifndef ALIGN +#define ALIGN(a) \ + (((size_t)a + (sizeof(double) - 1)) & (~(sizeof(double) - 1))) +#endif /* ALIGN */ + +typedef struct _Blt_ChainLink ChainLink; +typedef struct _Blt_Chain Chain; + +/* + *--------------------------------------------------------------------------- + * + * Blt_Chain_Create -- + * + * Creates a new linked list (chain) structure and initializes its + * pointers; + * + * Results: + * Returns a pointer to the newly created chain structure. + * + *--------------------------------------------------------------------------- + */ +Blt_Chain +Blt_Chain_Create(void) +{ + Chain *chainPtr; + + chainPtr = Blt_Malloc(sizeof(Chain)); + if (chainPtr != NULL) { + Blt_Chain_Init(chainPtr); + } + return chainPtr; +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_Chain_AllocLink -- + * + * Creates a new chain link. Unlink Blt_Chain_NewLink, this routine also + * allocates extra memory in the node for data. + * + * Results: + * The return value is the pointer to the newly created entry. + * + *--------------------------------------------------------------------------- + */ +Blt_ChainLink +Blt_Chain_AllocLink(size_t extraSize) +{ + ChainLink *linkPtr; + size_t linkSize; + + linkSize = ALIGN(sizeof(ChainLink)); + linkPtr = Blt_AssertCalloc(1, linkSize + extraSize); + if (extraSize > 0) { + /* Point clientData at the memory beyond the normal structure. */ + linkPtr->clientData = (ClientData)((char *)linkPtr + linkSize); + } + return linkPtr; +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_Chain_InitLink -- + * + * Initializes the new link. This routine is for applications that use + * their own memory allocation procedures to allocate links. + * + * Results: + * None. + * + *--------------------------------------------------------------------------- + */ +void +Blt_Chain_InitLink(ChainLink *linkPtr) +{ + linkPtr->clientData = NULL; + linkPtr->next = linkPtr->prev = NULL; +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_Chain_NewLink -- + * + * Creates a new link. + * + * Results: + * The return value is the pointer to the newly created link. + * + *--------------------------------------------------------------------------- + */ +Blt_ChainLink +Blt_Chain_NewLink(void) +{ + ChainLink *linkPtr; + + linkPtr = Blt_AssertMalloc(sizeof(ChainLink)); + linkPtr->clientData = NULL; + linkPtr->next = linkPtr->prev = NULL; + return linkPtr; +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_Chain_Reset -- + * + * Removes all the links in the chain, freeing the memory used for each + * link. Memory pointed to by the link (clientData) is not freed. It's + * the caller's responsibility to deallocate it. + * + * Results: + * None. + * + *--------------------------------------------------------------------------- + */ +void +Blt_Chain_Reset(Chain *chainPtr) /* Chain to clear */ +{ + if (chainPtr != NULL) { + ChainLink *oldPtr; + ChainLink *linkPtr = chainPtr->head; + + while (linkPtr != NULL) { + oldPtr = linkPtr; + linkPtr = linkPtr->next; + Blt_Free(oldPtr); + } + Blt_Chain_Init(chainPtr); + } +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_Chain_Destroy + * + * Frees all the nodes in the chain and deallocates the memory used for + * the chain structure itself. It's assumed that the chain was previously + * allocated by Blt_Chain_Create. + * + * Results: + * None. + * + *--------------------------------------------------------------------------- + */ +void +Blt_Chain_Destroy(Chain *chainPtr) +{ + if (chainPtr != NULL) { + Blt_Chain_Reset(chainPtr); + Blt_Free(chainPtr); + } +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_Chain_Init -- + * + * Initializes a linked list. + * + * Results: + * None. + * + *--------------------------------------------------------------------------- + */ +void +Blt_Chain_Init(Chain *chainPtr) +{ + chainPtr->nLinks = 0; + chainPtr->head = chainPtr->tail = NULL; +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_Chain_LinkAfter -- + * + * Inserts a link after another link. If afterPtr is NULL, then the new + * link is prepended to the beginning of the chain. + * + * Results: + * None. + * + *--------------------------------------------------------------------------- + */ +void +Blt_Chain_LinkAfter(Chain *chainPtr, ChainLink *linkPtr, ChainLink *afterPtr) +{ + if (chainPtr->head == NULL) { + chainPtr->tail = chainPtr->head = linkPtr; + } else { + if (afterPtr == NULL) { + /* Append to the end of the chain. */ + linkPtr->next = NULL; + linkPtr->prev = chainPtr->tail; + chainPtr->tail->next = linkPtr; + chainPtr->tail = linkPtr; + } else { + linkPtr->next = afterPtr->next; + linkPtr->prev = afterPtr; + if (afterPtr == chainPtr->tail) { + chainPtr->tail = linkPtr; + } else { + afterPtr->next->prev = linkPtr; + } + afterPtr->next = linkPtr; + } + } + chainPtr->nLinks++; +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_Chain_LinkBefore -- + * + * Inserts a new link preceding a given link in a chain. If beforePtr is + * NULL, then the new link is placed at the beginning of the list. + * + * Results: + * None. + * + *--------------------------------------------------------------------------- + */ +void +Blt_Chain_LinkBefore(Chain *chainPtr, ChainLink *linkPtr, ChainLink *beforePtr) +{ + if (chainPtr->head == NULL) { + chainPtr->tail = chainPtr->head = linkPtr; + } else { + if (beforePtr == NULL) { + /* Prepend to the front of the chain */ + linkPtr->next = chainPtr->head; + linkPtr->prev = NULL; + chainPtr->head->prev = linkPtr; + chainPtr->head = linkPtr; + } else { + linkPtr->prev = beforePtr->prev; + linkPtr->next = beforePtr; + if (beforePtr == chainPtr->head) { + chainPtr->head = linkPtr; + } else { + beforePtr->prev->next = linkPtr; + } + beforePtr->prev = linkPtr; + } + } + chainPtr->nLinks++; +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_Chain_UnlinkLink -- + * + * Unlinks a link from the chain. The link is not deallocated, but only + * removed from the chain. + * + * Results: + * None. + * + *--------------------------------------------------------------------------- + */ +void +Blt_Chain_UnlinkLink(Chain *chainPtr, ChainLink *linkPtr) +{ + int unlinked; /* Indicates if the link is actually removed + * from the chain. */ + + unlinked = FALSE; + if (chainPtr->head == linkPtr) { + chainPtr->head = linkPtr->next; + unlinked = TRUE; + } + if (chainPtr->tail == linkPtr) { + chainPtr->tail = linkPtr->prev; + unlinked = TRUE; + } + if (linkPtr->next != NULL) { + linkPtr->next->prev = linkPtr->prev; + unlinked = TRUE; + } + if (linkPtr->prev != NULL) { + linkPtr->prev->next = linkPtr->next; + unlinked = TRUE; + } + if (unlinked) { + chainPtr->nLinks--; + } + linkPtr->prev = linkPtr->next = NULL; +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_Chain_DeleteLink -- + * + * Unlinks and frees the given link from the chain. It's assumed that + * the link belong to the chain. No error checking is performed to verify + * this. + * + * Results: + * None. + * + *--------------------------------------------------------------------------- + */ +void +Blt_Chain_DeleteLink(Blt_Chain chain, Blt_ChainLink link) +{ + Blt_Chain_UnlinkLink(chain, link); + Blt_Free(link); +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_Chain_Append + * + * Creates and new link with the given data and appends it to the end of + * the chain. + * + * Results: + * Returns a pointer to the link created. + * + *--------------------------------------------------------------------------- + */ +Blt_ChainLink +Blt_Chain_Append(Blt_Chain chain, ClientData clientData) +{ + Blt_ChainLink link; + + link = Blt_Chain_NewLink(); + Blt_Chain_LinkAfter(chain, link, (Blt_ChainLink)NULL); + Blt_Chain_SetValue(link, clientData); + return link; +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_Chain_Prepend + * + * Creates and new link with the given data and prepends it to beginning + * of the chain. + * + * Results: + * Returns a pointer to the link created. + * + *--------------------------------------------------------------------------- + */ +Blt_ChainLink +Blt_Chain_Prepend(Blt_Chain chain, ClientData clientData) +{ + Blt_ChainLink link; + + link = Blt_Chain_NewLink(); + Blt_Chain_LinkBefore(chain, link, (Blt_ChainLink)NULL); + Blt_Chain_SetValue(link, clientData); + return link; +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_Chain_GetNthLink -- + * + * Find the link at the given position in the chain. The position + * is number from 0. If position is negative is returns the nth + * link from the back of the chain. + * + * Results: + * Returns the pointer to the link, if that numbered link + * exists. Otherwise NULL. + * + *--------------------------------------------------------------------------- + */ +Blt_ChainLink +Blt_Chain_GetNthLink(Chain *chainPtr, long position) +{ + if (chainPtr != NULL) { + if (position < 0) { + ChainLink *linkPtr; + int i; + + position = -position; + for (i = 0, linkPtr = chainPtr->tail; linkPtr != NULL; + linkPtr = linkPtr->prev, i++) { + if (i == position) { + return linkPtr; + } + } + } else { + ChainLink *linkPtr; + int i; + + linkPtr = chainPtr->head; + for (i = 0, linkPtr = chainPtr->head; linkPtr != NULL; + linkPtr = linkPtr->next, i++) { + if (i == position) { + return linkPtr; + } + } + } + } + return NULL; +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_Chain_Sort -- + * + * Sorts the chain according to the given comparison routine. + * + * Results: + * None. + * + * Side Effects: + * The chain is reordered. + * + *--------------------------------------------------------------------------- + */ +void +Blt_Chain_Sort(Chain *chainPtr, Blt_ChainCompareProc *proc) +{ + ChainLink **linkArr; + ChainLink *linkPtr; + long i; + + if (chainPtr->nLinks < 2) { + return; + } + linkArr = Blt_Malloc(sizeof(Blt_ChainLink) * (chainPtr->nLinks + 1)); + if (linkArr == NULL) { + return; /* Out of memory. */ + } + i = 0; + for (linkPtr = chainPtr->head; linkPtr != NULL; + linkPtr = linkPtr->next) { + linkArr[i++] = linkPtr; + } + qsort((char *)linkArr, chainPtr->nLinks, sizeof(Blt_ChainLink), + (QSortCompareProc *)proc); + + /* Rethread the chain. */ + linkPtr = linkArr[0]; + chainPtr->head = linkPtr; + linkPtr->prev = NULL; + for (i = 1; i < chainPtr->nLinks; i++) { + linkPtr->next = linkArr[i]; + linkPtr->next->prev = linkPtr; + linkPtr = linkPtr->next; + } + chainPtr->tail = linkPtr; + linkPtr->next = NULL; + Blt_Free(linkArr); +} + + +/* + *--------------------------------------------------------------------------- + * + * Blt_Chain_IsBefore -- + * + * + * Results: + * Return boolean value if the first link comes before the second. + * + *--------------------------------------------------------------------------- + */ +int +Blt_Chain_IsBefore(ChainLink *firstPtr, ChainLink *lastPtr) +{ + ChainLink *linkPtr; + + for (linkPtr = firstPtr; linkPtr != NULL; linkPtr = linkPtr->next) { + if (linkPtr == lastPtr) { + return TRUE; + } + } + return FALSE; +} + diff --git a/src/bltChain.h b/src/bltChain.h new file mode 100644 index 0000000..e0b0259 --- /dev/null +++ b/src/bltChain.h @@ -0,0 +1,90 @@ + +/* + * bltChain.h -- + * + * Copyright 1993-2004 George A Howlett. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY + * KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS + * OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef _BLT_CHAIN_H +#define _BLT_CHAIN_H + +typedef struct _Blt_Chain *Blt_Chain; +typedef struct _Blt_ChainLink *Blt_ChainLink; + +/* + * A Blt_ChainLink is the container structure for the Blt_Chain. + */ + +struct _Blt_ChainLink { + Blt_ChainLink prev; /* Link to the previous link */ + Blt_ChainLink next; /* Link to the next link */ + ClientData clientData; /* Pointer to the data object */ +}; + +typedef int (Blt_ChainCompareProc)(Blt_ChainLink *l1Ptr, Blt_ChainLink *l2Ptr); + +/* + * A Blt_Chain is a doubly chained list structure. + */ +struct _Blt_Chain { + Blt_ChainLink head; /* Pointer to first element in chain */ + Blt_ChainLink tail; /* Pointer to last element in chain */ + long nLinks; /* Number of elements in chain */ +}; + +BLT_EXTERN void Blt_Chain_Init(Blt_Chain chain); +BLT_EXTERN Blt_Chain Blt_Chain_Create(void); +BLT_EXTERN void Blt_Chain_Destroy(Blt_Chain chain); +BLT_EXTERN Blt_ChainLink Blt_Chain_NewLink(void); +BLT_EXTERN Blt_ChainLink Blt_Chain_AllocLink(size_t size); +BLT_EXTERN Blt_ChainLink Blt_Chain_Append(Blt_Chain chain, + ClientData clientData); +BLT_EXTERN Blt_ChainLink Blt_Chain_Prepend(Blt_Chain chain, + ClientData clientData); +BLT_EXTERN void Blt_Chain_Reset(Blt_Chain chain); +BLT_EXTERN void Blt_Chain_InitLink(Blt_ChainLink link); +BLT_EXTERN void Blt_Chain_LinkAfter(Blt_Chain chain, Blt_ChainLink link, + Blt_ChainLink after); +BLT_EXTERN void Blt_Chain_LinkBefore(Blt_Chain chain, Blt_ChainLink link, + Blt_ChainLink before); +BLT_EXTERN void Blt_Chain_UnlinkLink(Blt_Chain chain, Blt_ChainLink link); +BLT_EXTERN void Blt_Chain_DeleteLink(Blt_Chain chain, Blt_ChainLink link); +BLT_EXTERN Blt_ChainLink Blt_Chain_GetNthLink(Blt_Chain chain, long position); +BLT_EXTERN void Blt_Chain_Sort(Blt_Chain chain, Blt_ChainCompareProc *proc); +BLT_EXTERN int Blt_Chain_IsBefore(Blt_ChainLink first, Blt_ChainLink last); + +#define Blt_Chain_GetLength(c) (((c) == NULL) ? 0 : (c)->nLinks) +#define Blt_Chain_FirstLink(c) (((c) == NULL) ? NULL : (c)->head) +#define Blt_Chain_LastLink(c) (((c) == NULL) ? NULL : (c)->tail) +#define Blt_Chain_PrevLink(l) ((l)->prev) +#define Blt_Chain_NextLink(l) ((l)->next) +#define Blt_Chain_GetValue(l) ((l)->clientData) +#define Blt_Chain_FirstValue(c) (((c)->head == NULL) ? NULL : (c)->head->clientData) +#define Blt_Chain_SetValue(l, value) ((l)->clientData = (ClientData)(value)) +#define Blt_Chain_AppendLink(c, l) \ + (Blt_Chain_LinkAfter((c), (l), (Blt_ChainLink)NULL)) +#define Blt_Chain_PrependLink(c, l) \ + (Blt_Chain_LinkBefore((c), (l), (Blt_ChainLink)NULL)) + +#endif /* _BLT_CHAIN_H */ diff --git a/src/bltConfig.C b/src/bltConfig.C new file mode 100644 index 0000000..cf67a23 --- /dev/null +++ b/src/bltConfig.C @@ -0,0 +1,2493 @@ + +/* + * bltConfig.c -- + * + * This file contains a Tcl_Obj based replacement for the widget + * configuration functions in Tk. + * + * Copyright (c) 1990-1994 The Regents of the University of California. + * Copyright (c) 1994-1997 Sun Microsystems, Inc. + * + * See the file "license.terms" for information on usage and redistribution + * of this file, and for a DISCLAIMER OF ALL WARRANTIES. + * + * Copyright 2003-2004 George A Howlett. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY + * KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS + * OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* + * This is a Tcl_Obj based replacement for the widget configuration + * functions in Tk. + * + * What not use the new Tk_Option interface? + * + * There were design changes in the new Tk_Option interface that + * make it unwieldy. + * + * o You have to dynamically allocate, store, and deallocate + * your option table. + * o The Tk_FreeConfigOptions routine requires a tkwin argument. + * Unfortunately, most widgets save the display pointer and + * de-reference their tkwin when the window is destroyed. + * o There's no TK_CONFIG_CUSTOM functionality. This means that + * save special options must be saved as strings by + * Tk_ConfigureWidget and processed later, thus losing the + * benefits of Tcl_Objs. It also make error handling + * problematic, since you don't pick up certain errors like + * + * .widget configure -myoption bad -myoption good + * + * You will never see the first "bad" value. + * o Especially compared to the former Tk_ConfigureWidget calls, + * the new interface is overly complex. If there was a big + * performance win, it might be worth the effort. But let's + * face it, this biggest wins are in processing custom options + * values with thousands of elements. Most common resources + * (font, color, etc) have string tokens anyways. + * + * On the other hand, the replacement functions in this file fell + * into place quite easily both from the aspect of API writer and + * user. The biggest benefit is that you don't need to change lots + * of working code just to get the benefits of Tcl_Objs. + * + */ + +#include "bltInt.h" + +#include <stdarg.h> +#include "bltFont.h" +#include "bltPicture.h" +#include "bltBgStyle.h" + +#if (_TK_VERSION < _VERSION(8,1,0)) +/* + *--------------------------------------------------------------------------- + * + * Tk_GetAnchorFromObj -- + * + * Return a Tk_Anchor value based on the value of the objPtr. + * + * Results: + * The return value is a standard TCL result. If an error occurs during + * conversion, an error message is left in the interpreter's result + * unless "interp" is NULL. + * + * Side effects: + * The object gets converted by Tcl_GetIndexFromObj. + * + *--------------------------------------------------------------------------- + */ +int +Tk_GetAnchorFromObj( + Tcl_Interp *interp, /* Used for error reporting. */ + Tcl_Obj *objPtr, /* The object we are trying to get the + * value from. */ + Tk_Anchor *anchorPtr) /* Where to place the Tk_Anchor that + * corresponds to the string value of + * objPtr. */ +{ + return Tk_GetAnchor(interp, Tcl_GetString(objPtr), anchorPtr); +} + +/* + *--------------------------------------------------------------------------- + * + * Tk_GetJustifyFromObj -- + * + * Return a Tk_Justify value based on the value of the objPtr. + * + * Results: + * The return value is a standard TCL result. If an error occurs during + * conversion, an error message is left in the interpreter's result + * unless "interp" is NULL. + * + * Side effects: + * The object gets converted by Tcl_GetIndexFromObj. + * + *--------------------------------------------------------------------------- + */ +int +Tk_GetJustifyFromObj( + Tcl_Interp *interp, /* Used for error reporting. */ + Tcl_Obj *objPtr, /* The object we are trying to get the + * value from. */ + Tk_Justify *justifyPtr) /* Where to place the Tk_Justify that + * corresponds to the string value of + * objPtr. */ +{ + return Tk_GetJustify(interp, Tcl_GetString(objPtr), justifyPtr); +} +/* + *--------------------------------------------------------------------------- + * + * Tk_GetReliefFromObj -- + * + * Return an integer value based on the value of the objPtr. + * + * Results: + * The return value is a standard TCL result. If an error occurs during + * conversion, an error message is left in the interpreter's result + * unless "interp" is NULL. + * + * Side effects: + * The object gets converted by Tcl_GetIndexFromObj. + * + *--------------------------------------------------------------------------- + */ +int +Tk_GetReliefFromObj( + Tcl_Interp *interp, /* Used for error reporting. */ + Tcl_Obj *objPtr, /* The object we are trying to get the + * value from. */ + int *reliefPtr) /* Where to place the answer. */ +{ + return Tk_GetRelief(interp, Tcl_GetString(objPtr), reliefPtr); +} + + +/* + *--------------------------------------------------------------------------- + * + * Tk_Alloc3DBorderFromObj -- + * + * Given a Tcl_Obj *, map the value to a corresponding + * Tk_3DBorder structure based on the tkwin given. + * + * Results: + * The return value is a token for a data structure describing a + * 3-D border. This token may be passed to procedures such as + * Blt_Draw3DRectangle and Tk_Free3DBorder. If an error prevented + * the border from being created then NULL is returned and an error + * message will be left in the interp's result. + * + * Side effects: + * The border is added to an internal database with a reference + * count. For each call to this procedure, there should eventually + * be a call to FreeBorderObjProc so that the database is + * cleaned up when borders aren't in use anymore. + * + *--------------------------------------------------------------------------- + */ +Tk_3DBorder +Tk_Alloc3DBorderFromObj( + Tcl_Interp *interp, /* Interp for error results. */ + Tk_Window tkwin, /* Need the screen the border is used on.*/ + Tcl_Obj *objPtr) /* Object giving name of color for window + * background. */ +{ + return Tk_Get3DBorder(interp, tkwin, Tcl_GetString(objPtr)); +} +/* + *--------------------------------------------------------------------------- + * + * Tk_AllocBitmapFromObj -- + * + * Given a Tcl_Obj *, map the value to a corresponding + * Pixmap structure based on the tkwin given. + * + * Results: + * The return value is the X identifer for the desired bitmap + * (i.e. a Pixmap with a single plane), unless string couldn't be + * parsed correctly. In this case, None is returned and an error + * message is left in the interp's result. The caller should never + * modify the bitmap that is returned, and should eventually call + * Tk_FreeBitmapFromObj when the bitmap is no longer needed. + * + * Side effects: + * The bitmap is added to an internal database with a reference count. + * For each call to this procedure, there should eventually be a call + * to Tk_FreeBitmapFromObj, so that the database can be cleaned up + * when bitmaps aren't needed anymore. + * + *--------------------------------------------------------------------------- + */ +Pixmap +Tk_AllocBitmapFromObj( + Tcl_Interp *interp, /* Interp for error results. This may + * be NULL. */ + Tk_Window tkwin, /* Need the screen the bitmap is used on.*/ + Tcl_Obj *objPtr) /* Object describing bitmap; see manual + * entry for legal syntax of string value. */ +{ + return Tk_GetBitmap(interp, tkwin, Tcl_GetString(objPtr)); +} + +/* + *--------------------------------------------------------------------------- + * + * Tk_AllocFontFromObj -- + * + * Given a string description of a font, map the description to a + * corresponding Blt_Font that represents the font. + * + * Results: + * The return value is token for the font, or NULL if an error + * prevented the font from being created. If NULL is returned, an + * error message will be left in interp's result object. + * + * Side effects: + * The font is added to an internal database with a reference + * count. For each call to this procedure, there should eventually + * be a call to Blt_FreeFont() or Blt_FreeFontFromObj() so that the + * database is cleaned up when fonts aren't in use anymore. + * + *--------------------------------------------------------------------------- + */ +Tk_Font +Tk_AllocFontFromObj( + Tcl_Interp *interp, /* Interp for database and error return. */ + Tk_Window tkwin, /* For screen on which font will be used. */ + Tcl_Obj *objPtr) /* Object describing font, as: named font, + * native format, or parseable string. */ +{ + return Tk_GetFont(interp, tkwin, Tcl_GetString(objPtr)); +} + +/* + *--------------------------------------------------------------------------- + * + * Tk_AllocCursorFromObj -- + * + * Given a Tcl_Obj *, map the value to a corresponding + * Tk_Cursor structure based on the tkwin given. + * + * Results: + * The return value is the X identifer for the desired cursor, + * unless objPtr couldn't be parsed correctly. In this case, + * None is returned and an error message is left in the interp's result. + * The caller should never modify the cursor that is returned, and + * should eventually call Tk_FreeCursorFromObj when the cursor is no + * longer needed. + * + * Side effects: + * The cursor is added to an internal database with a reference count. + * For each call to this procedure, there should eventually be a call + * to Tk_FreeCursorFromObj, so that the database can be cleaned up + * when cursors aren't needed anymore. + * + *--------------------------------------------------------------------------- + */ +Tk_Cursor +Tk_AllocCursorFromObj( + Tcl_Interp *interp, /* Interp for error results. */ + Tk_Window tkwin, /* Window in which the cursor will be used.*/ + Tcl_Obj *objPtr) /* Object describing cursor; see manual + * entry for description of legal + * syntax of this obj's string rep. */ +{ + return Tk_GetCursor(interp, tkwin, Tcl_GetString(objPtr)); +} + +/* + *--------------------------------------------------------------------------- + * + * Tk_AllocColorFromObj -- + * + * Given a Tcl_Obj *, map the value to a corresponding + * XColor structure based on the tkwin given. + * + * Results: + * The return value is a pointer to an XColor structure that + * indicates the red, blue, and green intensities for the color + * given by the string in objPtr, and also specifies a pixel value + * to use to draw in that color. If an error occurs, NULL is + * returned and an error message will be left in interp's result + * (unless interp is NULL). + * + * Side effects: + * The color is added to an internal database with a reference count. + * For each call to this procedure, there should eventually be a call + * to Tk_FreeColorFromObj so that the database is cleaned up when colors + * aren't in use anymore. + * + *--------------------------------------------------------------------------- + */ +XColor * +Tk_AllocColorFromObj( + Tcl_Interp *interp, /* Used only for error reporting. If NULL, + * then no messages are provided. */ + Tk_Window tkwin, /* Window in which the color will be used.*/ + Tcl_Obj *objPtr) /* Object that describes the color; string + * value is a color name such as "red" or + * "#ff0000".*/ +{ + const char *string; + + string = Tcl_GetString(objPtr); + return Tk_GetColor(interp, tkwin, Tk_GetUid(string)); +} +#endif + + +/* + *--------------------------------------------------------------------------- + * + * Blt_GetPositionFromObj -- + * + * Convert a string representing a numeric position. + * A position can be in one of the following forms. + * + * number - number of the item in the hierarchy, indexed + * from zero. + * "end" - last position in the hierarchy. + * + * Results: + * A standard TCL result. If "string" is a valid index, then + * *indexPtr is filled with the corresponding numeric index. + * If "end" was selected then *indexPtr is set to -1. + * Otherwise an error message is left in interp->result. + * + * Side effects: + * None. + * + *--------------------------------------------------------------------------- + */ +int +Blt_GetPositionFromObj( + Tcl_Interp *interp, /* Interpreter to report results back + * to. */ + Tcl_Obj *objPtr, /* Tcl_Obj representation of the index. + * Can be an integer or "end" to refer + * to the last index. */ + long *indexPtr) /* Holds the converted index. */ +{ + const char *string; + + string = Tcl_GetString(objPtr); + if ((string[0] == 'e') && (strcmp(string, "end") == 0)) { + *indexPtr = -1; /* Indicates last position in hierarchy. */ + } else { + long position; + + if (Tcl_GetLongFromObj(interp, objPtr, &position) != TCL_OK) { + return TCL_ERROR; + } + if (position < 0) { + Tcl_AppendResult(interp, "bad position \"", string, "\"", + (char *)NULL); + return TCL_ERROR; + } + *indexPtr = position; + } + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_GetPixelsFromObj -- + * + * Like Tk_GetPixelsFromObj, but checks for negative, zero. + * + * Results: + * A standard TCL result. + * + *--------------------------------------------------------------------------- + */ +int +Blt_GetPixelsFromObj( + Tcl_Interp *interp, + Tk_Window tkwin, + Tcl_Obj *objPtr, + int check, /* Can be PIXELS_POS, PIXELS_NNEG, + * or PIXELS_ANY, */ + int *valuePtr) +{ + int length; + + if (Tk_GetPixelsFromObj(interp, tkwin, objPtr, &length) != TCL_OK) { + return TCL_ERROR; + } + if (length >= SHRT_MAX) { + Tcl_AppendResult(interp, "bad distance \"", Tcl_GetString(objPtr), + "\": too big to represent", (char *)NULL); + return TCL_ERROR; + } + switch (check) { + case PIXELS_NNEG: + if (length < 0) { + Tcl_AppendResult(interp, "bad distance \"", Tcl_GetString(objPtr), + "\": can't be negative", (char *)NULL); + return TCL_ERROR; + } + break; + + case PIXELS_POS: + if (length <= 0) { + Tcl_AppendResult(interp, "bad distance \"", Tcl_GetString(objPtr), + "\": must be positive", (char *)NULL); + return TCL_ERROR; + } + break; + + case PIXELS_ANY: + break; + } + *valuePtr = length; + return TCL_OK; +} + +int +Blt_GetPadFromObj( + Tcl_Interp *interp, /* Interpreter to send results back to */ + Tk_Window tkwin, /* Window */ + Tcl_Obj *objPtr, /* Pixel value string */ + Blt_Pad *padPtr) +{ + int side1, side2; + int objc; + Tcl_Obj **objv; + + if (Tcl_ListObjGetElements(interp, objPtr, &objc, &objv) != TCL_OK) { + return TCL_ERROR; + } + if ((objc < 1) || (objc > 2)) { + Tcl_AppendResult(interp, "wrong # elements in padding list", + (char *)NULL); + return TCL_ERROR; + } + if (Blt_GetPixelsFromObj(interp, tkwin, objv[0], PIXELS_NNEG, + &side1) != TCL_OK) { + return TCL_ERROR; + } + side2 = side1; + if ((objc > 1) && + (Blt_GetPixelsFromObj(interp, tkwin, objv[1], PIXELS_NNEG, + &side2) != TCL_OK)) { + return TCL_ERROR; + } + /* Don't update the pad structure until we know both values are okay. */ + padPtr->side1 = side1; + padPtr->side2 = side2; + return TCL_OK; +} + +int +Blt_GetStateFromObj( + Tcl_Interp *interp, /* Interpreter to send results back to */ + Tcl_Obj *objPtr, /* Pixel value string */ + int *statePtr) +{ + char c; + const char *string; + int length; + + string = Tcl_GetStringFromObj(objPtr, &length); + c = string[0]; + if ((c == 'n') && (strncmp(string, "normal", length) == 0)) { + *statePtr = STATE_NORMAL; + } else if ((c == 'd') && (strncmp(string, "disabled", length) == 0)) { + *statePtr = STATE_DISABLED; + } else if ((c == 'a') && (strncmp(string, "active", length) == 0)) { + *statePtr = STATE_ACTIVE; + } else { + Tcl_AppendResult(interp, "bad state \"", string, + "\": should be normal, active, or disabled", (char *)NULL); + return TCL_ERROR; + } + return TCL_OK; +} + +const char * +Blt_NameOfState(int state) +{ + switch (state) { + case STATE_ACTIVE: + return "active"; + case STATE_DISABLED: + return "disabled"; + case STATE_NORMAL: + return "normal"; + default: + return "???"; + } +} + + +/* + *--------------------------------------------------------------------------- + * + * Blt_NameOfFill -- + * + * Converts the integer representing the fill style into a string. + * + *--------------------------------------------------------------------------- + */ +const char * +Blt_NameOfFill(int fill) +{ + switch (fill) { + case FILL_X: + return "x"; + case FILL_Y: + return "y"; + case FILL_NONE: + return "none"; + case FILL_BOTH: + return "both"; + default: + return "unknown value"; + } +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_GetFillFromObj -- + * + * Converts the fill style string into its numeric representation. + * + * Valid style strings are: + * + * "none" Use neither plane. + * "x" X-coordinate plane. + * "y" Y-coordinate plane. + * "both" Use both coordinate planes. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +int +Blt_GetFillFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, int *fillPtr) +{ + char c; + const char *string; + int length; + + string = Tcl_GetStringFromObj(objPtr, &length); + c = string[0]; + if ((c == 'n') && (strncmp(string, "none", length) == 0)) { + *fillPtr = FILL_NONE; + } else if ((c == 'x') && (strncmp(string, "x", length) == 0)) { + *fillPtr = FILL_X; + } else if ((c == 'y') && (strncmp(string, "y", length) == 0)) { + *fillPtr = FILL_Y; + } else if ((c == 'b') && (strncmp(string, "both", length) == 0)) { + *fillPtr = FILL_BOTH; + } else { + Tcl_AppendResult(interp, "bad argument \"", string, + "\": should be \"none\", \"x\", \"y\", or \"both\"", (char *)NULL); + return TCL_ERROR; + } + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_NameOfResize -- + * + * Converts the resize value into its string representation. + * + * Results: + * Returns a pointer to the static name string. + * + *--------------------------------------------------------------------------- + */ +const char * +Blt_NameOfResize(int resize) +{ + switch (resize & RESIZE_BOTH) { + case RESIZE_NONE: + return "none"; + case RESIZE_EXPAND: + return "expand"; + case RESIZE_SHRINK: + return "shrink"; + case RESIZE_BOTH: + return "both"; + default: + return "unknown resize value"; + } +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_GetResizeFromObj -- + * + * Converts the resize string into its numeric representation. + * + * Valid style strings are: + * + * "none" + * "expand" + * "shrink" + * "both" + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +int +Blt_GetResizeFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, int *resizePtr) +{ + char c; + const char *string; + int length; + + string = Tcl_GetStringFromObj(objPtr, &length); + c = string[0]; + if ((c == 'n') && (strncmp(string, "none", length) == 0)) { + *resizePtr = RESIZE_NONE; + } else if ((c == 'b') && (strncmp(string, "both", length) == 0)) { + *resizePtr = RESIZE_BOTH; + } else if ((c == 'e') && (strncmp(string, "expand", length) == 0)) { + *resizePtr = RESIZE_EXPAND; + } else if ((c == 's') && (strncmp(string, "shrink", length) == 0)) { + *resizePtr = RESIZE_SHRINK; + } else { + Tcl_AppendResult(interp, "bad resize argument \"", string, + "\": should be \"none\", \"expand\", \"shrink\", or \"both\"", + (char *)NULL); + return TCL_ERROR; + } + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_GetDashesFromObj -- + * + * Converts a TCL list of dash values into a dash list ready for + * use with XSetDashes. + * + * A valid list dash values can have zero through 11 elements + * (PostScript limit). Values must be between 1 and 255. Although + * a list of 0 (like the empty string) means no dashes. + * + * Results: + * A standard TCL result. If the list represented a valid dash + * list TCL_OK is returned and *dashesPtr* will contain the + * valid dash list. Otherwise, TCL_ERROR is returned and + * interp->result will contain an error message. + * + * + *--------------------------------------------------------------------------- + */ +int +Blt_GetDashesFromObj( + Tcl_Interp *interp, + Tcl_Obj *objPtr, + Blt_Dashes *dashesPtr) +{ + const char *string; + char c; + + string = Tcl_GetString(objPtr); + if (string == NULL) { + dashesPtr->values[0] = 0; + return TCL_OK; + } + c = string[0]; + if (c == '\0') { + dashesPtr->values[0] = 0; + } else if ((c == 'd') && (strcmp(string, "dot") == 0)) { + /* 1 */ + dashesPtr->values[0] = 1; + dashesPtr->values[1] = 0; + } else if ((c == 'd') && (strcmp(string, "dash") == 0)) { + /* 5 2 */ + dashesPtr->values[0] = 5; + dashesPtr->values[1] = 2; + dashesPtr->values[2] = 0; + } else if ((c == 'd') && (strcmp(string, "dashdot") == 0)) { + /* 2 4 2 */ + dashesPtr->values[0] = 2; + dashesPtr->values[1] = 4; + dashesPtr->values[2] = 2; + dashesPtr->values[3] = 0; + } else if ((c == 'd') && (strcmp(string, "dashdotdot") == 0)) { + /* 2 4 2 2 */ + dashesPtr->values[0] = 2; + dashesPtr->values[1] = 4; + dashesPtr->values[2] = 2; + dashesPtr->values[3] = 2; + dashesPtr->values[4] = 0; + } else { + int objc; + Tcl_Obj **objv; + int i; + + if (Tcl_ListObjGetElements(interp, objPtr, &objc, &objv) != TCL_OK) { + return TCL_ERROR; + } + if (objc > 11) { /* This is the postscript limit */ + Tcl_AppendResult(interp, "too many values in dash list \"", + string, "\"", (char *)NULL); + return TCL_ERROR; + } + for (i = 0; i < objc; i++) { + int value; + + if (Tcl_GetIntFromObj(interp, objv[i], &value) != TCL_OK) { + return TCL_ERROR; + } + /* + * Backward compatibility: + * Allow list of 0 to turn off dashes + */ + if ((value == 0) && (objc == 1)) { + break; + } + if ((value < 1) || (value > 255)) { + Tcl_AppendResult(interp, "dash value \"", + Tcl_GetString(objv[i]), "\" is out of range", + (char *)NULL); + return TCL_ERROR; + } + dashesPtr->values[i] = (unsigned char)value; + } + /* Make sure the array ends with a NUL byte */ + dashesPtr->values[i] = 0; + } + return TCL_OK; +} + +const char * +Blt_NameOfSide(int side) +{ + switch (side) { + case SIDE_LEFT: + return "left"; + case SIDE_RIGHT: + return "right"; + case SIDE_BOTTOM: + return "bottom"; + case SIDE_TOP: + return "top"; + } + return "unknown side value"; +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_GetSideFromObj -- + * + * Converts the fill style string into its numeric representation. + * + * Valid style strings are "left", "right", "top", or "bottom". + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED */ +int +Blt_GetSideFromObj( + Tcl_Interp *interp, /* Interpreter to send results back to */ + Tcl_Obj *objPtr, /* Value string */ + int *sidePtr) /* (out) Token representing side: + * either SIDE_LEFT, SIDE_RIGHT, + * SIDE_TOP, or SIDE_BOTTOM. */ +{ + char c; + const char *string; + int length; + + string = Tcl_GetStringFromObj(objPtr, &length); + c = string[0]; + if ((c == 'l') && (strncmp(string, "left", length) == 0)) { + *sidePtr = SIDE_LEFT; + } else if ((c == 'r') && (strncmp(string, "right", length) == 0)) { + *sidePtr = SIDE_RIGHT; + } else if ((c == 't') && (strncmp(string, "top", length) == 0)) { + *sidePtr = SIDE_TOP; + } else if ((c == 'b') && (strncmp(string, "bottom", length) == 0)) { + *sidePtr = SIDE_BOTTOM; + } else { + Tcl_AppendResult(interp, "bad side \"", string, + "\": should be left, right, top, or bottom", (char *)NULL); + return TCL_ERROR; + } + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_ResetLimits -- + * + * Resets the limits to their default values. + * + * Results: + * None. + * + *--------------------------------------------------------------------------- + */ +void +Blt_ResetLimits(Blt_Limits *limitsPtr) /* Limits to be imposed on the value */ +{ + limitsPtr->flags = 0; + limitsPtr->min = LIMITS_MIN; + limitsPtr->max = LIMITS_MAX; + limitsPtr->nom = LIMITS_NOM; +} + +int +Blt_GetLimitsFromObj(Tcl_Interp *interp, Tk_Window tkwin, Tcl_Obj *objPtr, + Blt_Limits *limitsPtr) +{ + int values[3]; + int nValues; + int limitsFlags; + + /* Initialize limits to default values */ + values[2] = LIMITS_NOM; + values[1] = LIMITS_MAX; + values[0] = LIMITS_MIN; + limitsFlags = 0; + nValues = 0; + if (objPtr != NULL) { + Tcl_Obj **objv; + int objc; + int i; + + if (Tcl_ListObjGetElements(interp, objPtr, &objc, &objv) != TCL_OK) { + return TCL_ERROR; + } + if (objc > 3) { + Tcl_AppendResult(interp, "wrong # limits \"", Tcl_GetString(objPtr), + "\"", (char *)NULL); + return TCL_ERROR; + } + for (i = 0; i < objc; i++) { + const char *string; + int size; + + string = Tcl_GetString(objv[i]); + if (string[0] == '\0') { + continue; /* Empty string: use default value */ + } + limitsFlags |= (1 << i); + if (Tk_GetPixelsFromObj(interp, tkwin, objv[i], &size) != TCL_OK) { + return TCL_ERROR; + } + if ((size < LIMITS_MIN) || (size > LIMITS_MAX)) { + Tcl_AppendResult(interp, "bad limit \"", string, "\"", + (char *)NULL); + return TCL_ERROR; + } + values[i] = size; + } + nValues = objc; + } + /* + * Check the limits specified. We can't check the requested size of + * widgets. + */ + switch (nValues) { + case 1: + limitsFlags |= (LIMITS_MIN_SET | LIMITS_MAX_SET); + values[1] = values[0]; /* Set minimum and maximum to value */ + break; + + case 2: + if (values[1] < values[0]) { + Tcl_AppendResult(interp, "bad range \"", Tcl_GetString(objPtr), + "\": min > max", (char *)NULL); + return TCL_ERROR; /* Minimum is greater than maximum */ + } + break; + + case 3: + if (values[1] < values[0]) { + Tcl_AppendResult(interp, "bad range \"", Tcl_GetString(objPtr), + "\": min > max", (char *)NULL); + return TCL_ERROR; /* Minimum is greater than maximum */ + } + if ((values[2] < values[0]) || (values[2] > values[1])) { + Tcl_AppendResult(interp, "nominal value \"", Tcl_GetString(objPtr), + "\" out of range", (char *)NULL); + return TCL_ERROR; /* Nominal is outside of range defined + * by minimum and maximum */ + } + break; + } + limitsPtr->min = values[0]; + limitsPtr->max = values[1]; + limitsPtr->nom = values[2]; + limitsPtr->flags = limitsFlags; + return TCL_OK; +} + +/* Configuration option helper routines */ + +/* + *--------------------------------------------------------------------------- + * + * DoConfig -- + * + * This procedure applies a single configuration option + * to a widget record. + * + * Results: + * A standard TCL return value. + * + * Side effects: + * WidgRec is modified as indicated by specPtr and value. + * The old value is recycled, if that is appropriate for + * the value type. + * + *--------------------------------------------------------------------------- + */ +static int +DoConfig( + Tcl_Interp *interp, /* Interpreter for error reporting. */ + Tk_Window tkwin, /* Window containing widget (needed to + * set up X resources). */ + Blt_ConfigSpec *sp, /* Specifier to apply. */ + Tcl_Obj *objPtr, /* Value to use to fill in widgRec. */ + char *widgRec) /* Record whose fields are to be + * modified. Values must be properly + * initialized. */ +{ + char *ptr; + int objIsEmpty; + + objIsEmpty = FALSE; + if (objPtr == NULL) { + objIsEmpty = TRUE; + } else if (sp->specFlags & BLT_CONFIG_NULL_OK) { + int length; + + if (objPtr->bytes != NULL) { + length = objPtr->length; + } else { + Tcl_GetStringFromObj(objPtr, &length); + } + objIsEmpty = (length == 0); + } + do { + ptr = widgRec + sp->offset; + switch (sp->type) { + case BLT_CONFIG_ANCHOR: + { + Tk_Anchor anchor; + + if (Tk_GetAnchorFromObj(interp, objPtr, &anchor) != TCL_OK) { + return TCL_ERROR; + } + *(Tk_Anchor *)ptr = anchor; + } + break; + + case BLT_CONFIG_BITMAP: + { + Pixmap bitmap; + + if (objIsEmpty) { + bitmap = None; + } else { + bitmap = Tk_AllocBitmapFromObj(interp, tkwin, objPtr); + if (bitmap == None) { + return TCL_ERROR; + } + } + if (*(Pixmap *)ptr != None) { + Tk_FreeBitmap(Tk_Display(tkwin), *(Pixmap *)ptr); + } + *(Pixmap *)ptr = bitmap; + } + break; + + case BLT_CONFIG_BOOLEAN: + { + int bool; + + if (Tcl_GetBooleanFromObj(interp, objPtr, &bool) != TCL_OK) { + return TCL_ERROR; + } + *(int *)ptr = bool; + } + break; + + case BLT_CONFIG_BORDER: + { + Tk_3DBorder border; + + if (objIsEmpty) { + border = NULL; + } else { + border = Tk_Alloc3DBorderFromObj(interp, tkwin, objPtr); + if (border == NULL) { + return TCL_ERROR; + } + } + if (*(Tk_3DBorder *)ptr != NULL) { + Tk_Free3DBorder(*(Tk_3DBorder *)ptr); + } + *(Tk_3DBorder *)ptr = border; + } + break; + + case BLT_CONFIG_CAP_STYLE: + { + int cap; + Tk_Uid uid; + + uid = Tk_GetUid(Tcl_GetString(objPtr)); + if (Tk_GetCapStyle(interp, uid, &cap) != TCL_OK) { + return TCL_ERROR; + } + *(int *)ptr = cap; + } + break; + + case BLT_CONFIG_COLOR: + { + XColor *color; + + if (objIsEmpty) { + color = NULL; + } else { + color = Tk_GetColor(interp, tkwin, + Tk_GetUid(Tcl_GetString(objPtr))); + if (color == NULL) { + return TCL_ERROR; + } + } + if (*(XColor **)ptr != NULL) { + Tk_FreeColor(*(XColor **)ptr); + } + *(XColor **)ptr = color; + } + break; + + case BLT_CONFIG_CURSOR: + case BLT_CONFIG_ACTIVE_CURSOR: + { + Tk_Cursor cursor; + + if (objIsEmpty) { + cursor = None; + } else { + cursor = Tk_AllocCursorFromObj(interp, tkwin, objPtr); + if (cursor == None) { + return TCL_ERROR; + } + } + if (*(Tk_Cursor *)ptr != None) { + Tk_FreeCursor(Tk_Display(tkwin), *(Tk_Cursor *)ptr); + } + *(Tk_Cursor *)ptr = cursor; + if (sp->type == BLT_CONFIG_ACTIVE_CURSOR) { + Tk_DefineCursor(tkwin, cursor); + } + } + break; + + case BLT_CONFIG_CUSTOM: + if ((*sp->customPtr->parseProc)(sp->customPtr->clientData, interp, + tkwin, objPtr, widgRec, sp->offset, sp->specFlags) != TCL_OK) { + return TCL_ERROR; + } + break; + + case BLT_CONFIG_DOUBLE: + { + double value; + + if (Tcl_GetDoubleFromObj(interp, objPtr, &value) != TCL_OK) { + return TCL_ERROR; + } + *(double *)ptr = value; + } + break; + + case BLT_CONFIG_FONT: + { + Blt_Font font; + + if (objIsEmpty) { + font = NULL; + } else { + font = Blt_AllocFontFromObj(interp, tkwin, objPtr); + if (font == NULL) { + return TCL_ERROR; + } + } + if (*(Blt_Font *)ptr != NULL) { + Blt_FreeFont(*(Blt_Font *)ptr); + } + *(Blt_Font *)ptr = font; + } + break; + + case BLT_CONFIG_TK_FONT: + { + Tk_Font font; + + if (objIsEmpty) { + font = NULL; + } else { + font = Tk_AllocFontFromObj(interp, tkwin, objPtr); + if (font == NULL) { + return TCL_ERROR; + } + } + if (*(Tk_Font *)ptr != NULL) { + Tk_FreeFont(*(Tk_Font *)ptr); + } + *(Tk_Font *)ptr = font; + } + break; + + case BLT_CONFIG_INT: + { + int value; + + if (Tcl_GetIntFromObj(interp, objPtr, &value) != TCL_OK) { + return TCL_ERROR; + } + *(int *)ptr = value; + } + break; + + case BLT_CONFIG_JOIN_STYLE: + { + int join; + Tk_Uid uid; + + uid = Tk_GetUid(Tcl_GetString(objPtr)); + if (Tk_GetJoinStyle(interp, uid, &join) != TCL_OK) { + return TCL_ERROR; + } + *(int *)ptr = join; + } + break; + + case BLT_CONFIG_JUSTIFY: + { + Tk_Justify justify; + + if (Tk_GetJustifyFromObj(interp, objPtr, &justify) != TCL_OK) { + return TCL_ERROR; + } + *(Tk_Justify *)ptr = justify; + } + break; + + case BLT_CONFIG_MM: + { + double value; + + if (Tk_GetMMFromObj(interp, tkwin, objPtr, &value) != TCL_OK) { + return TCL_ERROR; + } + *(double *)ptr = value; + } + break; + + + case BLT_CONFIG_RELIEF: + { + int relief; + + if (Tk_GetReliefFromObj(interp, objPtr, &relief) != TCL_OK) { + return TCL_ERROR; + } + *(int *)ptr = relief; + } + break; + + case BLT_CONFIG_STRING: + { + char *value; + + value = (objIsEmpty) ? NULL : + Blt_AssertStrdup(Tcl_GetString(objPtr)); + if (*(char **)ptr != NULL) { + Blt_Free(*(char **)ptr); + } + *(char **)ptr = value; + } + break; + + case BLT_CONFIG_UID: + if (*(Blt_Uid *)ptr != NULL) { + Blt_FreeUid(*(Blt_Uid *)ptr); + } + if (objIsEmpty) { + *(Blt_Uid *)ptr = NULL; + } else { + *(Blt_Uid *)ptr = Blt_GetUid(Tcl_GetString(objPtr)); + } + break; + + case BLT_CONFIG_WINDOW: + { + Tk_Window tkwin2; + + if (objIsEmpty) { + tkwin2 = None; + } else { + const char *path; + + path = Tcl_GetString(objPtr); + tkwin2 = Tk_NameToWindow(interp, path, tkwin); + if (tkwin2 == NULL) { + return TCL_ERROR; + } + } + *(Tk_Window *)ptr = tkwin2; + } + break; + + case BLT_CONFIG_BITMASK: + { + int bool; + unsigned long mask, flags; + + if (Tcl_GetBooleanFromObj(interp, objPtr, &bool) != TCL_OK) { + return TCL_ERROR; + } + mask = (unsigned long)sp->customPtr; + flags = *(int *)ptr; + flags &= ~mask; + if (bool) { + flags |= mask; + } + *(int *)ptr = flags; + } + break; + + case BLT_CONFIG_BITMASK_INVERT: + { + int bool; + unsigned long mask, flags; + + if (Tcl_GetBooleanFromObj(interp, objPtr, &bool) != TCL_OK) { + return TCL_ERROR; + } + mask = (unsigned long)sp->customPtr; + flags = *(int *)ptr; + flags &= ~mask; + if (!bool) { + flags |= mask; + } + *(int *)ptr = flags; + } + break; + + case BLT_CONFIG_DASHES: + if (Blt_GetDashesFromObj(interp, objPtr, (Blt_Dashes *)ptr) + != TCL_OK) { + return TCL_ERROR; + } + break; + + + case BLT_CONFIG_FILL: + if (Blt_GetFillFromObj(interp, objPtr, (int *)ptr) != TCL_OK) { + return TCL_ERROR; + } + break; + + case BLT_CONFIG_RESIZE: + if (Blt_GetResizeFromObj(interp, objPtr, (int *)ptr) != TCL_OK) { + return TCL_ERROR; + } + break; + + case BLT_CONFIG_FLOAT: + { + double value; + + if (Tcl_GetDoubleFromObj(interp, objPtr, &value) != TCL_OK) { + return TCL_ERROR; + } + *(float *)ptr = (float)value; + } + break; + + case BLT_CONFIG_INT_NNEG: + { + long value; + + if (Blt_GetCountFromObj(interp, objPtr, COUNT_NNEG, + &value) != TCL_OK) { + return TCL_ERROR; + } + *(int *)ptr = (int)value; + } + break; + + + case BLT_CONFIG_INT_POS: + { + long value; + + if (Blt_GetCountFromObj(interp, objPtr, COUNT_POS, &value) + != TCL_OK) { + return TCL_ERROR; + } + *(int *)ptr = (int)value; + } + break; + + + case BLT_CONFIG_LIST: + { + const char **argv; + int argc; + + if (Tcl_SplitList(interp, Tcl_GetString(objPtr), &argc, &argv) + != TCL_OK) { + return TCL_ERROR; + } + if (*(char ***)ptr != NULL) { + Blt_Free(*(char ***)ptr); + } + *(const char ***)ptr = argv; + } + break; + + case BLT_CONFIG_LONG: + { + long value; + + if (Tcl_GetLongFromObj(interp, objPtr, &value) != TCL_OK) { + return TCL_ERROR; + } + *(long *)ptr = value; + } + break; + + case BLT_CONFIG_LONG_NNEG: + { + long value; + + if (Blt_GetCountFromObj(interp, objPtr, COUNT_NNEG, + &value) != TCL_OK) { + return TCL_ERROR; + } + *(long *)ptr = value; + } + break; + + + case BLT_CONFIG_LONG_POS: + { + long value; + + if (Blt_GetCountFromObj(interp, objPtr, COUNT_POS, &value) + != TCL_OK) { + return TCL_ERROR; + } + *(long *)ptr = value; + } + break; + + case BLT_CONFIG_OBJ: + { + Tcl_IncrRefCount(objPtr); + if (*(Tcl_Obj **)ptr != NULL) { + Tcl_DecrRefCount(*(Tcl_Obj **)ptr); + } + *(Tcl_Obj **)ptr = objPtr; + } + break; + + case BLT_CONFIG_PAD: + if (Blt_GetPadFromObj(interp, tkwin, objPtr, (Blt_Pad *)ptr) + != TCL_OK) { + return TCL_ERROR; + } + break; + + case BLT_CONFIG_PIXELS_NNEG: + { + int value; + + if (Blt_GetPixelsFromObj(interp, tkwin, objPtr, + PIXELS_NNEG, &value) != TCL_OK) { + return TCL_ERROR; + } + *(int *)ptr = value; + } + break; + + case BLT_CONFIG_PIXELS: + { + int value; + + if (Blt_GetPixelsFromObj(interp, tkwin, objPtr, PIXELS_ANY, + &value) != TCL_OK) { + return TCL_ERROR; + } + *(int *)ptr = value; + } + break; + + case BLT_CONFIG_PIXELS_POS: + { + int value; + + if (Blt_GetPixelsFromObj(interp, tkwin, objPtr, PIXELS_POS, + &value) != TCL_OK) { + return TCL_ERROR; + } + *(int *)ptr = value; + } + break; + + case BLT_CONFIG_STATE: + if (Blt_GetStateFromObj(interp, objPtr, (int *)ptr) != TCL_OK) { + return TCL_ERROR; + } + break; + + case BLT_CONFIG_SIDE: + if (Blt_GetSideFromObj(interp, objPtr, (int *)ptr) != TCL_OK) { + return TCL_ERROR; + } + break; + + case BLT_CONFIG_BACKGROUND: + { + Blt_Background style; + + if (objIsEmpty) { + style = NULL; + } else { + style = Blt_GetBackgroundFromObj(interp, tkwin, objPtr); + if (style == NULL) { + return TCL_ERROR; + } + } + if (*(Blt_Background *)ptr != NULL) { + Blt_FreeBackground(*(Blt_Background *)ptr); + } + *(Blt_Background *)ptr = style; + } + break; + + case BLT_CONFIG_PIX32: + if (Blt_GetPixelFromObj(interp, objPtr, (Blt_Pixel *)ptr)!=TCL_OK) { + return TCL_ERROR; + } + break; + + default: + Tcl_AppendResult(interp, "bad config table: unknown type ", + Blt_Itoa(sp->type), (char *)NULL); + return TCL_ERROR; + } + sp++; + } while ((sp->switchName == NULL) && (sp->type != BLT_CONFIG_END)); + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * FormatConfigValue -- + * + * This procedure formats the current value of a configuration + * option. + * + * Results: + * The return value is the formatted value of the option given + * by specPtr and widgRec. If the value is static, so that it + * need not be freed, *freeProcPtr will be set to NULL; otherwise + * *freeProcPtr will be set to the address of a procedure to + * free the result, and the caller must invoke this procedure + * when it is finished with the result. + * + * Side effects: + * None. + * + *--------------------------------------------------------------------------- + */ +static Tcl_Obj * +FormatConfigValue( + Tcl_Interp *interp, /* Interpreter for use in real conversions. */ + Tk_Window tkwin, /* Window corresponding to widget. */ + Blt_ConfigSpec *sp, /* Pointer to information describing option. + * Must not point to a synonym option. */ + char *widgRec) /* Pointer to record holding current + * values of info for widget. */ +{ + char *ptr; + const char *string; + + ptr = widgRec + sp->offset; + string = ""; + switch (sp->type) { + case BLT_CONFIG_ANCHOR: + string = Tk_NameOfAnchor(*(Tk_Anchor *)ptr); + break; + + case BLT_CONFIG_BITMAP: + if (*(Pixmap *)ptr != None) { + string = Tk_NameOfBitmap(Tk_Display(tkwin), *(Pixmap *)ptr); + } + break; + + case BLT_CONFIG_BOOLEAN: + return Tcl_NewBooleanObj(*(int *)ptr); + + case BLT_CONFIG_BORDER: + if (*(Tk_3DBorder *)ptr != NULL) { + string = Tk_NameOf3DBorder(*(Tk_3DBorder *)ptr); + } + break; + + case BLT_CONFIG_CAP_STYLE: + string = Tk_NameOfCapStyle(*(int *)ptr); + break; + + case BLT_CONFIG_COLOR: + if (*(XColor **)ptr != NULL) { + string = Tk_NameOfColor(*(XColor **)ptr); + } + break; + + case BLT_CONFIG_CURSOR: + case BLT_CONFIG_ACTIVE_CURSOR: + if (*(Tk_Cursor *)ptr != None) { + string = Tk_NameOfCursor(Tk_Display(tkwin), *(Tk_Cursor *)ptr); + } + break; + + case BLT_CONFIG_CUSTOM: + return (*sp->customPtr->printProc) + (sp->customPtr->clientData, interp, tkwin, widgRec, + sp->offset, sp->specFlags); + + case BLT_CONFIG_DOUBLE: + return Tcl_NewDoubleObj(*(double *)ptr); + + case BLT_CONFIG_FONT: + if (*(Blt_Font *)ptr != NULL) { + string = Blt_NameOfFont(*(Blt_Font *)ptr); + } + break; + + case BLT_CONFIG_TK_FONT: + if (*(Tk_Font *)ptr != NULL) { + string = Tk_NameOfFont(*(Tk_Font *)ptr); + } + break; + + case BLT_CONFIG_INT: + return Tcl_NewIntObj(*(int *)ptr); + + case BLT_CONFIG_JOIN_STYLE: + string = Tk_NameOfJoinStyle(*(int *)ptr); + break; + + case BLT_CONFIG_JUSTIFY: + string = Tk_NameOfJustify(*(Tk_Justify *)ptr); + break; + + case BLT_CONFIG_MM: + return Tcl_NewDoubleObj(*(double *)ptr); + + case BLT_CONFIG_PIXELS: + case BLT_CONFIG_PIXELS_POS: + case BLT_CONFIG_PIXELS_NNEG: + return Tcl_NewIntObj(*(int *)ptr); + + case BLT_CONFIG_RELIEF: + string = Tk_NameOfRelief(*(int *)ptr); + break; + + case BLT_CONFIG_STRING: + case BLT_CONFIG_UID: + if (*(char **)ptr != NULL) { + string = *(char **)ptr; + } + break; + + case BLT_CONFIG_BITMASK: + { + unsigned long flag; + + flag = (*(unsigned long *)ptr) & (unsigned long)sp->customPtr; + return Tcl_NewBooleanObj((flag != 0)); + } + + case BLT_CONFIG_BITMASK_INVERT: + { + unsigned long flag; + + flag = (*(unsigned long *)ptr) & (unsigned long)sp->customPtr; + return Tcl_NewBooleanObj((flag == 0)); + } + + case BLT_CONFIG_DASHES: + { + unsigned char *p; + Tcl_Obj *listObjPtr; + Blt_Dashes *dashesPtr = (Blt_Dashes *)ptr; + + listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL); + for(p = dashesPtr->values; *p != 0; p++) { + Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewIntObj(*p)); + } + return listObjPtr; + } + + case BLT_CONFIG_INT_NNEG: + case BLT_CONFIG_INT_POS: + return Tcl_NewIntObj(*(int *)ptr); + + case BLT_CONFIG_FILL: + string = Blt_NameOfFill(*(int *)ptr); + break; + + case BLT_CONFIG_RESIZE: + string = Blt_NameOfResize(*(int *)ptr); + break; + + case BLT_CONFIG_FLOAT: + { + double x = *(float *)ptr; + return Tcl_NewDoubleObj(x); + } + + case BLT_CONFIG_LIST: + { + Tcl_Obj *objPtr, *listObjPtr; + char *const *p; + + listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL); + for (p = *(char ***)ptr; *p != NULL; p++) { + objPtr = Tcl_NewStringObj(*p, -1); + Tcl_ListObjAppendElement(interp, listObjPtr, objPtr); + } + return listObjPtr; + } + + case BLT_CONFIG_LONG: + return Tcl_NewLongObj(*(long *)ptr); + + case BLT_CONFIG_LONG_NNEG: + case BLT_CONFIG_LONG_POS: + return Tcl_NewLongObj(*(long *)ptr); + + case BLT_CONFIG_OBJ: + if (*(Tcl_Obj **)ptr != NULL) { + return *(Tcl_Obj **)ptr; + } + break; + + case BLT_CONFIG_PAD: + { + Blt_Pad *padPtr = (Blt_Pad *)ptr; + Tcl_Obj *objPtr, *listObjPtr; + + listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL); + objPtr = Tcl_NewIntObj(padPtr->side1); + Tcl_ListObjAppendElement(interp, listObjPtr, objPtr); + objPtr = Tcl_NewIntObj(padPtr->side2); + Tcl_ListObjAppendElement(interp, listObjPtr, objPtr); + return listObjPtr; + } + + case BLT_CONFIG_STATE: + string = Blt_NameOfState(*(int *)ptr); + break; + + case BLT_CONFIG_SIDE: + string = Blt_NameOfSide(*(int *)ptr); + break; + + case BLT_CONFIG_BACKGROUND: + if (*(Blt_Background *)ptr != NULL) { + string = Blt_NameOfBackground(*(Blt_Background *)ptr); + } + break; + + case BLT_CONFIG_PIX32: + string = Blt_NameOfPixel((Blt_Pixel *)ptr); + break; + + default: + string = "?? unknown type ??"; + } + return Tcl_NewStringObj(string, -1); +} + +/* + *--------------------------------------------------------------------------- + * + * FormatConfigInfo -- + * + * Create a valid TCL list holding the configuration information + * for a single configuration option. + * + * Results: + * A TCL list, dynamically allocated. The caller is expected to + * arrange for this list to be freed eventually. + * + * Side effects: + * Memory is allocated. + * + *--------------------------------------------------------------------------- + */ +static Tcl_Obj * +FormatConfigInfo( + Tcl_Interp *interp, /* Interpreter to use for things + * like floating-point precision. */ + Tk_Window tkwin, /* Window corresponding to widget. */ + Blt_ConfigSpec *sp, /* Pointer to information describing + * option. */ + char *widgRec) /* Pointer to record holding current + * values of info for widget. */ +{ + Tcl_Obj *listObjPtr; + + listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL); + if (sp->switchName != NULL) { + Tcl_ListObjAppendElement(interp, listObjPtr, + Tcl_NewStringObj(sp->switchName, -1)); + } else { + Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewStringObj("", -1)); + } + if (sp->dbName != NULL) { + Tcl_ListObjAppendElement(interp, listObjPtr, + Tcl_NewStringObj(sp->dbName, -1)); + } else { + Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewStringObj("", -1)); + } + if (sp->type == BLT_CONFIG_SYNONYM) { + return listObjPtr; + } + if (sp->dbClass != NULL) { + Tcl_ListObjAppendElement(interp, listObjPtr, + Tcl_NewStringObj(sp->dbClass, -1)); + } else { + Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewStringObj("", -1)); + } + if (sp->defValue != NULL) { + Tcl_ListObjAppendElement(interp, listObjPtr, + Tcl_NewStringObj(sp->defValue, -1)); + } else { + Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewStringObj("", -1)); + } + Tcl_ListObjAppendElement(interp, listObjPtr, + FormatConfigValue(interp, tkwin, sp, widgRec)); + return listObjPtr; +} + +/* + *--------------------------------------------------------------------------- + * + * FindConfigSpec -- + * + * Search through a table of configuration specs, looking for + * one that matches a given switchName. + * + * Results: + * The return value is a pointer to the matching entry, or NULL + * if nothing matched. In that case an error message is left + * in the interp's result. + * + * Side effects: + * None. + * + *--------------------------------------------------------------------------- + */ +static Blt_ConfigSpec * +FindConfigSpec( + Tcl_Interp *interp, /* Used for reporting errors. */ + Blt_ConfigSpec *specs, /* Pointer to table of configuration + * specifications for a widget. */ + Tcl_Obj *objPtr, /* Name (suitable for use in a "config" + * command) identifying particular option. */ + int needFlags, /* Flags that must be present in matching + * entry. */ + int hateFlags) /* Flags that must NOT be present in + * matching entry. */ +{ + Blt_ConfigSpec *matchPtr; /* Matching spec, or NULL. */ + Blt_ConfigSpec *sp; + const char *string; + char c; /* First character of current argument. */ + int length; + + string = Tcl_GetStringFromObj(objPtr, &length); + c = string[1]; + matchPtr = NULL; + for (sp = specs; sp->type != BLT_CONFIG_END; sp++) { + if (sp->switchName == NULL) { + continue; + } + if ((sp->switchName[1] != c) || + (strncmp(sp->switchName, string, length) != 0)) { + continue; + } + if (((sp->specFlags & needFlags) != needFlags) || + (sp->specFlags & hateFlags)) { + continue; + } + if (sp->switchName[length] == 0) { + matchPtr = sp; + goto gotMatch; + } + if (matchPtr != NULL) { + if (interp != NULL) { + Tcl_AppendResult(interp, "ambiguous option \"", string, "\"", + (char *)NULL); + } + return (Blt_ConfigSpec *)NULL; + } + matchPtr = sp; + } + + if (matchPtr == NULL) { + if (interp != NULL) { + Tcl_AppendResult(interp, "unknown option \"", string, "\"", + (char *)NULL); + } + return (Blt_ConfigSpec *)NULL; + } + + /* + * Found a matching entry. If it's a synonym, then find the + * entry that it's a synonym for. + */ + + gotMatch: + sp = matchPtr; + if (sp->type == BLT_CONFIG_SYNONYM) { + for (sp = specs; /*empty*/; sp++) { + if (sp->type == BLT_CONFIG_END) { + if (interp != NULL) { + Tcl_AppendResult(interp, + "couldn't find synonym for option \"", string, "\"", + (char *)NULL); + } + return (Blt_ConfigSpec *) NULL; + } + if ((sp->dbName == matchPtr->dbName) && + (sp->type != BLT_CONFIG_SYNONYM) && + ((sp->specFlags & needFlags) == needFlags) && + !(sp->specFlags & hateFlags)) { + break; + } + } + } + return sp; +} + +/* Public routines */ + +/* + *--------------------------------------------------------------------------- + * + * Blt_ConfigureWidgetFromObj -- + * + * Process command-line options and database options to + * fill in fields of a widget record with resources and + * other parameters. + * + * Results: + * A standard TCL return value. In case of an error, + * the interp's result will hold an error message. + * + * Side effects: + * The fields of widgRec get filled in with information + * from argc/argv and the option database. Old information + * in widgRec's fields gets recycled. + * + *--------------------------------------------------------------------------- + */ +int +Blt_ConfigureWidgetFromObj( + Tcl_Interp *interp, /* Interpreter for error reporting. */ + Tk_Window tkwin, /* Window containing widget (needed to + * set up X resources). */ + Blt_ConfigSpec *specs, /* Describes legal options. */ + int objc, /* Number of elements in argv. */ + Tcl_Obj *const *objv, /* Command-line options. */ + char *widgRec, /* Record whose fields are to be + * modified. Values must be properly + * initialized. */ + int flags) /* Used to specify additional flags + * that must be present in config specs + * for them to be considered. Also, + * may have BLT_CONFIG_OBJV_ONLY set. */ +{ + Blt_ConfigSpec *sp; + int needFlags; /* Specs must contain this set of flags + * or else they are not considered. */ + int hateFlags; /* If a spec contains any bits here, it's + * not considered. */ + int result; + + if (tkwin == NULL) { + /* + * Either we're not really in Tk, or the main window was destroyed and + * we're on our way out of the application + */ + Tcl_AppendResult(interp, "NULL main window", (char *)NULL); + return TCL_ERROR; + } + + needFlags = flags & ~(BLT_CONFIG_USER_BIT - 1); + if (Tk_Depth(tkwin) <= 1) { + hateFlags = BLT_CONFIG_COLOR_ONLY; + } else { + hateFlags = BLT_CONFIG_MONO_ONLY; + } + + /* + * Pass one: scan through all the option specs, replacing strings + * with Tk_Uid structs (if this hasn't been done already) and + * clearing the BLT_CONFIG_OPTION_SPECIFIED flags. + */ + + for (sp = specs; sp->type != BLT_CONFIG_END; sp++) { + if (!(sp->specFlags & INIT) && (sp->switchName != NULL)) { + if (sp->dbName != NULL) { + sp->dbName = Tk_GetUid(sp->dbName); + } + if (sp->dbClass != NULL) { + sp->dbClass = Tk_GetUid(sp->dbClass); + } + if (sp->defValue != NULL) { + sp->defValue = Tk_GetUid(sp->defValue); + } + } + sp->specFlags = (sp->specFlags & ~BLT_CONFIG_OPTION_SPECIFIED) | INIT; + } + + /* + * Pass two: scan through all of the arguments, processing those + * that match entries in the specs. + */ + while (objc > 0) { + sp = FindConfigSpec(interp, specs, objv[0], needFlags, hateFlags); + if (sp == NULL) { + return TCL_ERROR; + } + + /* Process the entry. */ + if (objc < 2) { + Tcl_AppendResult(interp, "value for \"", Tcl_GetString(objv[0]), + "\" missing", (char *)NULL); + return TCL_ERROR; + } + if (DoConfig(interp, tkwin, sp, objv[1], widgRec) != TCL_OK) { + char msg[100]; + + sprintf_s(msg, 100, "\n (processing \"%.40s\" option)", + sp->switchName); + Tcl_AddErrorInfo(interp, msg); + return TCL_ERROR; + } + sp->specFlags |= BLT_CONFIG_OPTION_SPECIFIED; + objc -= 2, objv += 2; + } + + /* + * Pass three: scan through all of the specs again; if no + * command-line argument matched a spec, then check for info + * in the option database. If there was nothing in the + * database, then use the default. + */ + + if ((flags & BLT_CONFIG_OBJV_ONLY) == 0) { + Tcl_Obj *objPtr; + + for (sp = specs; sp->type != BLT_CONFIG_END; sp++) { + if ((sp->specFlags & BLT_CONFIG_OPTION_SPECIFIED) || + (sp->switchName == NULL) || (sp->type == BLT_CONFIG_SYNONYM)) { + continue; + } + if (((sp->specFlags & needFlags) != needFlags) || + (sp->specFlags & hateFlags)) { + continue; + } + objPtr = NULL; + if (sp->dbName != NULL) { + Tk_Uid value; + + /* If a resource name was specified, check if there's + * also a value was associated with it. This + * overrides the default value. */ + value = Tk_GetOption(tkwin, sp->dbName, sp->dbClass); + if (value != NULL) { + objPtr = Tcl_NewStringObj(value, -1); + } + } + + if (objPtr != NULL) { + Tcl_IncrRefCount(objPtr); + result = DoConfig(interp, tkwin, sp, objPtr, widgRec); + Tcl_DecrRefCount(objPtr); + if (result != TCL_OK) { + char msg[200]; + + sprintf_s(msg, 200, + "\n (%s \"%.50s\" in widget \"%.50s\")", + "database entry for", sp->dbName, Tk_PathName(tkwin)); + Tcl_AddErrorInfo(interp, msg); + return TCL_ERROR; + } + } else if ((sp->defValue != NULL) && + ((sp->specFlags & BLT_CONFIG_DONT_SET_DEFAULT) == 0)) { + + /* No resource value is found, use the default value. */ + objPtr = Tcl_NewStringObj(sp->defValue, -1); + Tcl_IncrRefCount(objPtr); + result = DoConfig(interp, tkwin, sp, objPtr, widgRec); + Tcl_DecrRefCount(objPtr); + if (result != TCL_OK) { + char msg[200]; + + sprintf_s(msg, 200, + "\n (%s \"%.50s\" in widget \"%.50s\")", + "default value for", sp->dbName, Tk_PathName(tkwin)); + Tcl_AddErrorInfo(interp, msg); + return TCL_ERROR; + } + } + } + } + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_ConfigureInfoFromObj -- + * + * Return information about the configuration options + * for a window, and their current values. + * + * Results: + * Always returns TCL_OK. The interp's result will be modified + * hold a description of either a single configuration option + * available for "widgRec" via "specs", or all the configuration + * options available. In the "all" case, the result will + * available for "widgRec" via "specs". The result will + * be a list, each of whose entries describes one option. + * Each entry will itself be a list containing the option's + * name for use on command lines, database name, database + * class, default value, and current value (empty string + * if none). For options that are synonyms, the list will + * contain only two values: name and synonym name. If the + * "name" argument is non-NULL, then the only information + * returned is that for the named argument (i.e. the corresponding + * entry in the overall list is returned). + * + * Side effects: + * None. + * + *--------------------------------------------------------------------------- + */ + +int +Blt_ConfigureInfoFromObj( + Tcl_Interp *interp, /* Interpreter for error reporting. */ + Tk_Window tkwin, /* Window corresponding to widgRec. */ + Blt_ConfigSpec *specs, /* Describes legal options. */ + char *widgRec, /* Record whose fields contain current + * values for options. */ + Tcl_Obj *objPtr, /* If non-NULL, indicates a single option + * whose info is to be returned. Otherwise + * info is returned for all options. */ + int flags) /* Used to specify additional flags + * that must be present in config specs + * for them to be considered. */ +{ + Blt_ConfigSpec *sp; + Tcl_Obj *listObjPtr, *valueObjPtr; + const char *string; + int needFlags, hateFlags; + + needFlags = flags & ~(BLT_CONFIG_USER_BIT - 1); + if (Tk_Depth(tkwin) <= 1) { + hateFlags = BLT_CONFIG_COLOR_ONLY; + } else { + hateFlags = BLT_CONFIG_MONO_ONLY; + } + + /* + * If information is only wanted for a single configuration + * spec, then handle that one spec specially. + */ + + Tcl_SetResult(interp, (char *)NULL, TCL_STATIC); + if (objPtr != NULL) { + sp = FindConfigSpec(interp, specs, objPtr, needFlags, hateFlags); + if (sp == NULL) { + return TCL_ERROR; + } + valueObjPtr = FormatConfigInfo(interp, tkwin, sp, widgRec); + Tcl_SetObjResult(interp, valueObjPtr); + return TCL_OK; + } + + /* + * Loop through all the specs, creating a big list with all + * their information. + */ + string = NULL; /* Suppress compiler warning. */ + if (objPtr != NULL) { + string = Tcl_GetString(objPtr); + } + listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL); + for (sp = specs; sp->type != BLT_CONFIG_END; sp++) { + if ((objPtr != NULL) && (sp->switchName != string)) { + continue; + } + if (((sp->specFlags & needFlags) != needFlags) || + (sp->specFlags & hateFlags)) { + continue; + } + if (sp->switchName == NULL) { + continue; + } + valueObjPtr = FormatConfigInfo(interp, tkwin, sp, widgRec); + Tcl_ListObjAppendElement(interp, listObjPtr, valueObjPtr); + } + Tcl_SetObjResult(interp, listObjPtr); + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_ConfigureValueFromObj -- + * + * This procedure returns the current value of a configuration + * option for a widget. + * + * Results: + * The return value is a standard TCL completion code (TCL_OK or + * TCL_ERROR). The interp's result will be set to hold either the value + * of the option given by objPtr (if TCL_OK is returned) or + * an error message (if TCL_ERROR is returned). + * + * Side effects: + * None. + * + *--------------------------------------------------------------------------- + */ +int +Blt_ConfigureValueFromObj( + Tcl_Interp *interp, /* Interpreter for error reporting. */ + Tk_Window tkwin, /* Window corresponding to widgRec. */ + Blt_ConfigSpec *specs, /* Describes legal options. */ + char *widgRec, /* Record whose fields contain current + * values for options. */ + Tcl_Obj *objPtr, /* Gives the command-line name for the + * option whose value is to be returned. */ + int flags) /* Used to specify additional flags + * that must be present in config specs + * for them to be considered. */ +{ + Blt_ConfigSpec *sp; + int needFlags, hateFlags; + + needFlags = flags & ~(BLT_CONFIG_USER_BIT - 1); + if (Tk_Depth(tkwin) <= 1) { + hateFlags = BLT_CONFIG_COLOR_ONLY; + } else { + hateFlags = BLT_CONFIG_MONO_ONLY; + } + sp = FindConfigSpec(interp, specs, objPtr, needFlags, hateFlags); + if (sp == NULL) { + return TCL_ERROR; + } + objPtr = FormatConfigValue(interp, tkwin, sp, widgRec); + Tcl_SetObjResult(interp, objPtr); + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_FreeOptions -- + * + * Free up all resources associated with configuration options. + * + * Results: + * None. + * + * Side effects: + * Any resource in widgRec that is controlled by a configuration + * option (e.g. a Tk_3DBorder or XColor) is freed in the appropriate + * fashion. + * + *--------------------------------------------------------------------------- + */ +void +Blt_FreeOptions( + Blt_ConfigSpec *specs, /* Describes legal options. */ + char *widgRec, /* Record whose fields contain current + * values for options. */ + Display *display, /* X display; needed for freeing some + * resources. */ + int needFlags) /* Used to specify additional flags + * that must be present in config specs + * for them to be considered. */ +{ + Blt_ConfigSpec *sp; + + for (sp = specs; sp->type != BLT_CONFIG_END; sp++) { + char *ptr; + + if ((sp->specFlags & needFlags) != needFlags) { + continue; + } + ptr = widgRec + sp->offset; + switch (sp->type) { + case BLT_CONFIG_STRING: + if (*((char **) ptr) != NULL) { + Blt_Free(*((char **) ptr)); + *((char **) ptr) = NULL; + } + break; + + case BLT_CONFIG_COLOR: + if (*((XColor **) ptr) != NULL) { + Tk_FreeColor(*((XColor **) ptr)); + *((XColor **) ptr) = NULL; + } + break; + + case BLT_CONFIG_FONT: + if (*((Blt_Font *) ptr) != None) { + Blt_FreeFont(*((Blt_Font *) ptr)); + *((Blt_Font *) ptr) = NULL; + } + break; + + case BLT_CONFIG_TK_FONT: + if (*((Tk_Font *) ptr) != None) { + Tk_FreeFont(*((Tk_Font *) ptr)); + *((Tk_Font *) ptr) = NULL; + } + break; + + case BLT_CONFIG_BITMAP: + if (*((Pixmap *) ptr) != None) { + Tk_FreeBitmap(display, *((Pixmap *) ptr)); + *((Pixmap *) ptr) = None; + } + break; + + case BLT_CONFIG_BORDER: + if (*((Tk_3DBorder *) ptr) != NULL) { + Tk_Free3DBorder(*((Tk_3DBorder *) ptr)); + *((Tk_3DBorder *) ptr) = NULL; + } + break; + + case BLT_CONFIG_CURSOR: + case BLT_CONFIG_ACTIVE_CURSOR: + if (*((Tk_Cursor *) ptr) != None) { + Tk_FreeCursor(display, *((Tk_Cursor *) ptr)); + *((Tk_Cursor *) ptr) = None; + } + break; + + case BLT_CONFIG_OBJ: + if (*(Tcl_Obj **)ptr != NULL) { + Tcl_DecrRefCount(*(Tcl_Obj **)ptr); + *(Tcl_Obj **)ptr = NULL; + } + break; + + case BLT_CONFIG_LIST: + if (*((char ***) ptr) != NULL) { + Blt_Free(*((char ***) ptr)); + *((char ***) ptr) = NULL; + } + break; + + case BLT_CONFIG_UID: + if (*(Blt_Uid *)ptr != NULL) { + Blt_FreeUid(*(Blt_Uid *)ptr); + *(Blt_Uid *)ptr = NULL; + } + break; + + case BLT_CONFIG_BACKGROUND: + if (*((Blt_Background *)ptr) != NULL) { + Blt_FreeBackground(*((Blt_Background *)ptr)); + *((Blt_Background *)ptr) = NULL; + } + break; + + case BLT_CONFIG_CUSTOM: + if ((sp->customPtr->freeProc != NULL) && (*(char **)ptr != NULL)) { + (*sp->customPtr->freeProc)(sp->customPtr->clientData, + display, widgRec, sp->offset); + } + break; + + } + } +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_ConfigModified -- + * + * Given the configuration specifications and one or more option + * patterns (terminated by a NULL), indicate if any of the matching + * configuration options has been reset. + * + * Results: + * Returns 1 if one of the options has changed, 0 otherwise. + * + *--------------------------------------------------------------------------- + */ +int +Blt_ConfigModified TCL_VARARGS_DEF(Blt_ConfigSpec *, arg1) +{ + va_list argList; + Blt_ConfigSpec *specs; + Blt_ConfigSpec *sp; + const char *option; + + specs = TCL_VARARGS_START(Blt_ConfigSpec *, arg1, argList); + while ((option = va_arg(argList, const char *)) != NULL) { + for (sp = specs; sp->type != BLT_CONFIG_END; sp++) { + if ((Tcl_StringMatch(sp->switchName, option)) && + (sp->specFlags & BLT_CONFIG_OPTION_SPECIFIED)) { + va_end(argList); + return 1; + } + } + } + va_end(argList); + return 0; +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_ConfigureComponentFromObj -- + * + * Configures a component of a widget. This is useful for + * widgets that have multiple components which aren't uniquely + * identified by a Tk_Window. It allows us, for example, set + * resources for axes of the graph widget. The graph really has + * only one window, but its convenient to specify components in a + * hierarchy of options. + * + * *graph.x.logScale yes + * *graph.Axis.logScale yes + * *graph.temperature.scaleSymbols yes + * *graph.Element.scaleSymbols yes + * + * This is really a hack to work around the limitations of the Tk + * resource database. It creates a temporary window, needed to + * call Tk_ConfigureWidget, using the name of the component. + * + * Results: + * A standard TCL result. + * + * Side Effects: + * A temporary window is created merely to pass to Tk_ConfigureWidget. + * + *--------------------------------------------------------------------------- + */ +int +Blt_ConfigureComponentFromObj( + Tcl_Interp *interp, + Tk_Window parent, /* Window to associate with component */ + const char *name, /* Name of component */ + const char *className, + Blt_ConfigSpec *sp, + int objc, + Tcl_Obj *const *objv, + char *widgRec, + int flags) +{ + Tk_Window tkwin; + int result; + char *tmpName; + int isTemporary = FALSE; + + tmpName = Blt_AssertStrdup(name); + + /* Window name can't start with an upper case letter */ + tmpName[0] = tolower(name[0]); + + /* + * Create component if a child window by the component's name + * doesn't already exist. + */ + tkwin = Blt_FindChild(parent, tmpName); + if (tkwin == NULL) { + tkwin = Tk_CreateWindow(interp, parent, tmpName, (char *)NULL); + isTemporary = TRUE; + } + if (tkwin == NULL) { + Tcl_AppendResult(interp, "can't find window in \"", + Tk_PathName(parent), "\"", (char *)NULL); + return TCL_ERROR; + } + assert(Tk_Depth(tkwin) == Tk_Depth(parent)); + Blt_Free(tmpName); + + Tk_SetClass(tkwin, className); + result = Blt_ConfigureWidgetFromObj(interp, tkwin, sp, objc, objv, widgRec, + flags); + if (isTemporary) { + Tk_DestroyWindow(tkwin); + } + return result; +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_ObjIsOption -- + * + * Indicates whether objPtr is a valid configuration option + * such as -background. + * + * Results: + * Returns 1 is a matching option is found and 0 otherwise. + * + *--------------------------------------------------------------------------- + */ +int +Blt_ObjIsOption( + Blt_ConfigSpec *specs, /* Describes legal options. */ + Tcl_Obj *objPtr, /* Command-line option name. */ + int flags) /* Used to specify additional flags + * that must be present in config specs + * for them to be considered. Also, + * may have BLT_CONFIG_OBJV_ONLY set. */ +{ + Blt_ConfigSpec *sp; + int needFlags; /* Specs must contain this set of flags + * or else they are not considered. */ + + needFlags = flags & ~(BLT_CONFIG_USER_BIT - 1); + sp = FindConfigSpec((Tcl_Interp *)NULL, specs, objPtr, needFlags, 0); + return (sp != NULL); +} diff --git a/src/bltConfig.h b/src/bltConfig.h new file mode 100644 index 0000000..7a19e9d --- /dev/null +++ b/src/bltConfig.h @@ -0,0 +1,406 @@ + +/* + * bltConfig.h -- + * + * Copyright 1993-2004 George A Howlett. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY + * KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS + * OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef BLT_CONFIG_H +#define BLT_CONFIG_H + +#ifdef HAVE_STDDEF_H +# include <stddef.h> +#endif /* HAVE_STDDEF_H */ + +#ifndef Blt_Offset +#ifdef offsetof +#define Blt_Offset(type, field) ((int) offsetof(type, field)) +#else +#define Blt_Offset(type, field) ((int) ((char *) &((type *) 0)->field)) +#endif +#endif /* Blt_Offset */ + + +typedef int (Blt_OptionParseProc)(ClientData clientData, Tcl_Interp *interp, + Tk_Window tkwin, Tcl_Obj *objPtr, char *widgRec, int offset, int flags); +typedef Tcl_Obj *(Blt_OptionPrintProc)(ClientData clientData, + Tcl_Interp *interp, Tk_Window tkwin, char *widgRec, int offset, + int flags); +typedef void (Blt_OptionFreeProc)(ClientData clientData, Display *display, + char *widgRec, int offset); + +typedef struct Blt_CustomOption { + Blt_OptionParseProc *parseProc; /* Procedure to call to parse + * an option and store it in + * converted form. */ + + Blt_OptionPrintProc *printProc; /* Procedure to return a + * Tcl_Obj representing an + * existing option value. */ + + Blt_OptionFreeProc *freeProc; /* Procedure used to free the + * value. */ + + ClientData clientData; /* Arbitrary one-word value + * used by option parser: + * passed to parseProc and + * printProc. */ +} Blt_CustomOption; + +/* + * Structure used to specify information for Tk_ConfigureWidget. Each + * structure gives complete information for one option, including + * how the option is specified on the command line, where it appears + * in the option database, etc. + */ + +typedef struct { + int type; /* Type of option, such as + * BLT_CONFIG_COLOR; see definitions + * below. Last option in table must + * have type BLT_CONFIG_END. */ + + const char *switchName; /* Switch used to specify option in + * argv. NULL means this spec is part + * of a group. */ + + Tk_Uid dbName; /* Name for option in option + * database. */ + + Tk_Uid dbClass; /* Class for option in database. */ + + Tk_Uid defValue; /* Default value for option if not + * specified in command line or + * database. */ + + int offset; /* Where in widget record to store + * value; use Blt_Offset macro to + * generate values for this. */ + + int specFlags; /* Any combination of the values + * defined below; other bits are used + * internally by tkConfig.c. */ + + Blt_CustomOption *customPtr; /* If type is BLT_CONFIG_CUSTOM then + * this is a pointer to info about how + * to parse and print the option. + * Otherwise it is irrelevant. */ +} Blt_ConfigSpec; + +/* + * Type values for Blt_ConfigSpec structures. See the user + * documentation for details. + */ +typedef enum { + BLT_CONFIG_ACTIVE_CURSOR, + BLT_CONFIG_ANCHOR, + BLT_CONFIG_BITMAP, + BLT_CONFIG_BOOLEAN, + BLT_CONFIG_BORDER, + BLT_CONFIG_CAP_STYLE, + BLT_CONFIG_COLOR, + BLT_CONFIG_CURSOR, + BLT_CONFIG_CUSTOM, + BLT_CONFIG_DOUBLE, + BLT_CONFIG_FONT, + BLT_CONFIG_INT, + BLT_CONFIG_JOIN_STYLE, + BLT_CONFIG_JUSTIFY, + BLT_CONFIG_MM, + BLT_CONFIG_RELIEF, + BLT_CONFIG_STRING, + BLT_CONFIG_SYNONYM, + BLT_CONFIG_UID, + BLT_CONFIG_WINDOW, + + BLT_CONFIG_BITMASK, + BLT_CONFIG_BITMASK_INVERT, + BLT_CONFIG_DASHES, + BLT_CONFIG_FILL, + BLT_CONFIG_FLOAT, + BLT_CONFIG_INT_NNEG, /* 0..N */ + BLT_CONFIG_INT_POS, /* 1..N */ + BLT_CONFIG_LIST, + BLT_CONFIG_LONG, + BLT_CONFIG_LONG_NNEG, /* 0..N */ + BLT_CONFIG_LONG_POS, /* 1..N */ + BLT_CONFIG_OBJ, + BLT_CONFIG_PAD, + BLT_CONFIG_PIXELS_NNEG, /* 1.1c 2m 3.2i excluding negative + values. */ + BLT_CONFIG_PIXELS_POS, /* 1.1c 2m 3.2i excluding negative + * values and zero. */ + BLT_CONFIG_PIXELS, /* 1.1c 2m 3.2i. */ + BLT_CONFIG_RESIZE, + BLT_CONFIG_SIDE, + BLT_CONFIG_STATE, + BLT_CONFIG_BACKGROUND, + BLT_CONFIG_PIX32, + + BLT_CONFIG_TK_FONT, + BLT_CONFIG_END +} Blt_ConfigTypes; + +/* + * Possible values for flags argument to Tk_ConfigureWidget: + */ +#define BLT_CONFIG_OBJV_ONLY 1 + +/* + * Possible flag values for Blt_ConfigSpec structures. Any bits at or + * above BLT_CONFIG_USER_BIT may be used by clients for selecting + * certain entries. Before changing any values here, coordinate with + * tkOldConfig.c (internal-use-only flags are defined there). + */ +/* + * Values for "flags" field of Blt_ConfigSpec structures. Be sure to + * coordinate these values with those defined in tk.h + * (BLT_CONFIG_COLOR_ONLY, etc.). There must not be overlap! + * + * INIT - Non-zero means (char *) things have been + * converted to Tk_Uid's. + */ +#define INIT (1<<0) +#define BLT_CONFIG_NULL_OK (1<<1) +#define BLT_CONFIG_COLOR_ONLY (1<<2) +#define BLT_CONFIG_MONO_ONLY (1<<3) +#define BLT_CONFIG_DONT_SET_DEFAULT (1<<4) +#define BLT_CONFIG_OPTION_SPECIFIED (1<<5) +#define BLT_CONFIG_USER_BIT (1<<8) + + +#define SIDE_LEFT (1<<0) +#define SIDE_TOP (1<<1) +#define SIDE_RIGHT (1<<2) +#define SIDE_BOTTOM (1<<3) + +#define STATE_NORMAL (0) +#define STATE_ACTIVE (1<<0) +#define STATE_DISABLED (1<<1) +#define STATE_EMPHASIS (1<<2) + +/* + *--------------------------------------------------------------------------- + * + * Blt_Pad -- + * + * Specifies vertical and horizontal padding. + * + * Padding can be specified on a per side basis. The fields + * side1 and side2 refer to the opposite sides, either + * horizontally or vertically. + * + * side1 side2 + * ----- ----- + * x | left right + * y | top bottom + * + *--------------------------------------------------------------------------- + */ +typedef struct { + unsigned short int side1, side2; +} Blt_Pad; + +#define padLeft xPad.side1 +#define padRight xPad.side2 +#define padTop yPad.side1 +#define padBottom yPad.side2 +#define PADDING(x) ((x).side1 + (x).side2) + +/* + *--------------------------------------------------------------------------- + * + * The following enumerated values are used as bit flags. + * FILL_NONE Neither coordinate plane is specified + * FILL_X Horizontal plane. + * FILL_Y Vertical plane. + * FILL_BOTH Both vertical and horizontal planes. + * + *--------------------------------------------------------------------------- + */ +#define FILL_NONE 0 +#define FILL_X 1 +#define FILL_Y 2 +#define FILL_BOTH 3 + +/* + * Resize -- + * + * These flags indicate in what ways each partition in a table can be + * resized from its default dimensions. The normal size of a row/column + * is the minimum amount of space needed to hold the widgets that span + * it. The table may then be stretched or shrunk depending if the + * container is larger or smaller than the table. This can occur if 1) + * the user resizes the toplevel widget, or 2) the container is in turn + * packed into a larger widget and the "fill" option is set. + * + * RESIZE_NONE - No resizing from normal size. + * RESIZE_EXPAND - Do not allow the size to decrease. + * The size may increase however. + * RESIZE_SHRINK - Do not allow the size to increase. + * The size may decrease however. + * RESIZE_BOTH - Allow the size to increase or + * decrease from the normal size. + * RESIZE_VIRGIN - Special case of the resize flag. Used to + * indicate the initial state of the flag. + * Empty rows/columns are treated differently + * if this row/column is set. + */ + +#define RESIZE_NONE 0 +#define RESIZE_EXPAND (1<<0) +#define RESIZE_SHRINK (1<<1) +#define RESIZE_BOTH (RESIZE_EXPAND | RESIZE_SHRINK) + +/* + *--------------------------------------------------------------------------- + * + * Blt_Dashes -- + * + * List of dash values (maximum 11 based upon PostScript limit). + * + *--------------------------------------------------------------------------- + */ +typedef struct { + unsigned char values[12]; + int offset; +} Blt_Dashes; + +#define LineIsDashed(d) ((d).values[0] != 0) + +/* + * Blt_Limits -- + * + * Defines the bounding of a size (width or height) in the paneset. It may + * be related to the widget, pane or paneset size. + */ +typedef struct { + int flags; /* Flags indicate whether using default + * values for limits or not. See flags + * below. */ + int max, min; /* Values for respective limits. */ + int nom; /* Nominal starting value. */ +} Blt_Limits; + +#define LIMITS_MIN_SET (1<<0) +#define LIMITS_MAX_SET (1<<1) +#define LIMITS_NOM_SET (1<<2) + +#define LIMITS_MIN 0 /* Default minimum limit */ +#define LIMITS_MAX SHRT_MAX /* Default maximum limit */ +#define LIMITS_NOM -1000 /* Default nomimal value. Indicates + * if a pane has received any space + * yet */ + +BLT_EXTERN void Blt_SetDashes (Display *display, GC gc, Blt_Dashes *dashesPtr); +BLT_EXTERN Blt_Dashes *Blt_GetDashes (GC gc); + +BLT_EXTERN void Blt_ResetLimits(Blt_Limits *limitsPtr); +BLT_EXTERN int Blt_GetLimitsFromObj(Tcl_Interp *interp, Tk_Window tkwin, + Tcl_Obj *objPtr, Blt_Limits *limitsPtr); + +BLT_EXTERN int Blt_ConfigureInfoFromObj(Tcl_Interp *interp, Tk_Window tkwin, + Blt_ConfigSpec *specs, char *widgRec, Tcl_Obj *objPtr, int flags); + +BLT_EXTERN int Blt_ConfigureValueFromObj(Tcl_Interp *interp, Tk_Window tkwin, + Blt_ConfigSpec *specs, char *widgRec, Tcl_Obj *objPtr, int flags); + +BLT_EXTERN int Blt_ConfigureWidgetFromObj(Tcl_Interp *interp, Tk_Window tkwin, + Blt_ConfigSpec *specs, int objc, Tcl_Obj *const *objv, char *widgRec, + int flags); + +BLT_EXTERN int Blt_ConfigureComponentFromObj(Tcl_Interp *interp, + Tk_Window tkwin, const char *name, const char *className, + Blt_ConfigSpec *specs, int objc, Tcl_Obj *const *objv, char *widgRec, + int flags); + +BLT_EXTERN int Blt_ConfigModified TCL_VARARGS(Blt_ConfigSpec *, specs); + +BLT_EXTERN const char *Blt_NameOfState(int state); +BLT_EXTERN const char *Blt_NameOfSide(int side); + +BLT_EXTERN void Blt_FreeOptions(Blt_ConfigSpec *specs, char *widgRec, + Display *display, int needFlags); + +BLT_EXTERN int Blt_ObjIsOption(Blt_ConfigSpec *specs, Tcl_Obj *objPtr, + int flags); + +BLT_EXTERN int Blt_GetSideFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, + int *sidePtr); + +BLT_EXTERN int Blt_GetPositionFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, + long *indexPtr); + +BLT_EXTERN int Blt_GetPixelsFromObj(Tcl_Interp *interp, Tk_Window tkwin, + Tcl_Obj *objPtr, int flags, int *valuePtr); + +BLT_EXTERN int Blt_GetPadFromObj(Tcl_Interp *interp, Tk_Window tkwin, + Tcl_Obj *objPtr, Blt_Pad *padPtr); + +BLT_EXTERN int Blt_GetStateFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, + int *statePtr); + +BLT_EXTERN int Blt_GetFillFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, + int *fillPtr); + +BLT_EXTERN int Blt_GetResizeFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, + int *fillPtr); + +BLT_EXTERN int Blt_GetDashesFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, + Blt_Dashes *dashesPtr); + +#if (_TK_VERSION < _VERSION(8,1,0)) +BLT_EXTERN int Tk_GetAnchorFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, + Tk_Anchor *anchorPtr); + +BLT_EXTERN int Tk_GetJustifyFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, + Tk_Justify *justifyPtr); + +BLT_EXTERN int Tk_GetReliefFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, + int *reliefPtr); + +BLT_EXTERN int Tk_GetMMFromObj(Tcl_Interp *interp, Tk_Window tkwin, + Tcl_Obj *objPtr, double *doublePtr); + +BLT_EXTERN int Tk_GetPixelsFromObj(Tcl_Interp *interp, Tk_Window tkwin, + Tcl_Obj *objPtr, int *intPtr); + +BLT_EXTERN Tk_3DBorder Tk_Alloc3DBorderFromObj(Tcl_Interp *interp, + Tk_Window tkwin, Tcl_Obj *objPtr); + +BLT_EXTERN Pixmap Tk_AllocBitmapFromObj(Tcl_Interp *interp, Tk_Window tkwin, + Tcl_Obj *objPtr); + +BLT_EXTERN Tk_Font Tk_AllocFontFromObj(Tcl_Interp *interp, Tk_Window tkwin, + Tcl_Obj *objPtr); + +BLT_EXTERN Tk_Cursor Tk_AllocCursorFromObj(Tcl_Interp *interp, Tk_Window tkwin, + Tcl_Obj *objPtr); + +BLT_EXTERN XColor *Tk_AllocColorFromObj(Tcl_Interp *interp, Tk_Window tkwin, + Tcl_Obj *objPtr); +#endif /* < 8.1 */ + +#endif /* BLT_CONFIG_H */ diff --git a/src/bltGrAxis.C b/src/bltGrAxis.C new file mode 100644 index 0000000..f6f54e9 --- /dev/null +++ b/src/bltGrAxis.C @@ -0,0 +1,5765 @@ + +/* + * bltGrAxis.c -- + * + * This module implements coordinate axes for the BLT graph widget. + * + * Copyright 1993-2004 George A Howlett. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include "bltGraph.h" +#include "bltOp.h" +#include "bltGrElem.h" +#include <X11/Xutil.h> + +#define MAXTICKS 10001 + +#define FCLAMP(x) ((((x) < 0.0) ? 0.0 : ((x) > 1.0) ? 1.0 : (x))) + +/* + * Round x in terms of units + */ +#define UROUND(x,u) (Round((x)/(u))*(u)) +#define UCEIL(x,u) (ceil((x)/(u))*(u)) +#define UFLOOR(x,u) (floor((x)/(u))*(u)) + +#define NUMDIGITS 15 /* Specifies the number of digits of + * accuracy used when outputting axis + * tick labels. */ +enum TickRange { + AXIS_TIGHT, AXIS_LOOSE, AXIS_ALWAYS_LOOSE +}; + +#define AXIS_PAD_TITLE 2 /* Padding for axis title. */ + +/* Axis flags: */ + +#define AXIS_AUTO_MAJOR (1<<16) /* Auto-generate major ticks. */ +#define AXIS_AUTO_MINOR (1<<17) /* Auto-generate minor ticks. */ +#define AXIS_USE (1<<18) /* Axis is displayed on the screen via + * the "use" operation */ +#define AXIS_GRID (1<<19) /* Display grid lines. */ +#define AXIS_GRIDMINOR (1<<20) /* Display grid lines for minor + * ticks. */ +#define AXIS_SHOWTICKS (1<<21) /* Display axis ticks. */ +#define AXIS_EXTERIOR (1<<22) /* Axis is exterior to the plot. */ +#define AXIS_CHECK_LIMITS (1<<23) /* Validate user-defined axis limits. */ + +#define HORIZMARGIN(m) (!((m)->site & 0x1)) /* Even sites are horizontal */ + +typedef struct { + int axis; /* Length of the axis. */ + int t1; /* Length of a major tick (in + * pixels). */ + int t2; /* Length of a minor tick (in + * pixels). */ + int label; /* Distance from axis to tick label. */ +} AxisInfo; + +typedef struct { + const char *name; + ClassId classId; + int margin, invertMargin; +} AxisName; + +static AxisName axisNames[] = { + { "x", CID_AXIS_X, MARGIN_BOTTOM, MARGIN_LEFT }, + { "y", CID_AXIS_Y, MARGIN_LEFT, MARGIN_BOTTOM }, + { "x2", CID_AXIS_X, MARGIN_TOP, MARGIN_RIGHT }, + { "y2", CID_AXIS_Y, MARGIN_RIGHT, MARGIN_TOP } +} ; +static int nAxisNames = sizeof(axisNames) / sizeof(AxisName); + +static Blt_OptionParseProc ObjToLimitProc; +static Blt_OptionPrintProc LimitToObjProc; +static Blt_CustomOption limitOption = { + ObjToLimitProc, LimitToObjProc, NULL, (ClientData)0 +}; + +static Blt_OptionFreeProc FreeTicksProc; +static Blt_OptionParseProc ObjToTicksProc; +static Blt_OptionPrintProc TicksToObjProc; +static Blt_CustomOption majorTicksOption = { + ObjToTicksProc, TicksToObjProc, FreeTicksProc, (ClientData)AXIS_AUTO_MAJOR, +}; +static Blt_CustomOption minorTicksOption = { + ObjToTicksProc, TicksToObjProc, FreeTicksProc, (ClientData)AXIS_AUTO_MINOR, +}; +static Blt_OptionFreeProc FreeAxisProc; +static Blt_OptionPrintProc AxisToObjProc; +static Blt_OptionParseProc ObjToAxisProc; +Blt_CustomOption bltXAxisOption = { + ObjToAxisProc, AxisToObjProc, FreeAxisProc, (ClientData)CID_AXIS_X +}; +Blt_CustomOption bltYAxisOption = { + ObjToAxisProc, AxisToObjProc, FreeAxisProc, (ClientData)CID_AXIS_Y +}; + +static Blt_OptionFreeProc FreeFormatProc; +static Blt_OptionParseProc ObjToFormatProc; +static Blt_OptionPrintProc FormatToObjProc; +static Blt_CustomOption formatOption = { + ObjToFormatProc, FormatToObjProc, FreeFormatProc, (ClientData)0, +}; +static Blt_OptionParseProc ObjToLooseProc; +static Blt_OptionPrintProc LooseToObjProc; +static Blt_CustomOption looseOption = { + ObjToLooseProc, LooseToObjProc, NULL, (ClientData)0, +}; + +static Blt_OptionParseProc ObjToUseProc; +static Blt_OptionPrintProc UseToObjProc; +static Blt_CustomOption useOption = { + ObjToUseProc, UseToObjProc, NULL, (ClientData)0 +}; + +#define DEF_AXIS_ACTIVEBACKGROUND STD_ACTIVE_BACKGROUND +#define DEF_AXIS_ACTIVEFOREGROUND STD_ACTIVE_FOREGROUND +#define DEF_AXIS_ACTIVERELIEF "flat" +#define DEF_AXIS_ANGLE "0.0" +#define DEF_AXIS_BACKGROUND (char *)NULL +#define DEF_AXIS_BORDERWIDTH "0" +#define DEF_AXIS_CHECKLIMITS "0" +#define DEF_AXIS_COMMAND (char *)NULL +#define DEF_AXIS_DESCENDING "0" +#define DEF_AXIS_FOREGROUND RGB_BLACK +#define DEF_AXIS_GRID_BARCHART "1" +#define DEF_AXIS_GRIDCOLOR RGB_GREY64 +#define DEF_AXIS_GRIDDASHES "dot" +#define DEF_AXIS_GRID_GRAPH "0" +#define DEF_AXIS_GRIDLINEWIDTH "0" +#define DEF_AXIS_GRIDMINOR "1" +#define DEF_AXIS_GRIDMINOR_COLOR RGB_GREY64 +#define DEF_AXIS_HIDE "0" +#define DEF_AXIS_JUSTIFY "c" +#define DEF_AXIS_LIMITS_FORMAT (char *)NULL +#define DEF_AXIS_LINEWIDTH "1" +#define DEF_AXIS_LOGSCALE "0" +#define DEF_AXIS_LOOSE "0" +#define DEF_AXIS_RANGE "0.0" +#define DEF_AXIS_RELIEF "flat" +#define DEF_AXIS_SCROLL_INCREMENT "10" +#define DEF_AXIS_SHIFTBY "0.0" +#define DEF_AXIS_SHOWTICKS "1" +#define DEF_AXIS_STEP "0.0" +#define DEF_AXIS_STEP "0.0" +#define DEF_AXIS_SUBDIVISIONS "2" +#define DEF_AXIS_TAGS "all" +#define DEF_AXIS_EXTERIOR "1" +#define DEF_AXIS_TICK_ANCHOR "c" +#define DEF_AXIS_LIMITS_FONT STD_FONT_NUMBERS +#define DEF_AXIS_TICKFONT_GRAPH STD_FONT_NUMBERS +#define DEF_AXIS_TICKFONT_BARCHART STD_FONT_SMALL +#define DEF_AXIS_TICKLENGTH "4" +#define DEF_AXIS_DIVISIONS "10" +#define DEF_AXIS_TITLE_ALTERNATE "0" +#define DEF_AXIS_TITLE_FG RGB_BLACK +#define DEF_AXIS_TITLE_FONT "{Sans Serif} 10" +#define DEF_AXIS_X_STEP_BARCHART "1.0" +#define DEF_AXIS_X_SUBDIVISIONS_BARCHART "0" + +static Blt_ConfigSpec configSpecs[] = +{ + {BLT_CONFIG_BACKGROUND, "-activebackground", "activeBackground", + "ActiveBackground", DEF_AXIS_ACTIVEBACKGROUND, + Blt_Offset(Axis, activeBg), ALL_GRAPHS | BLT_CONFIG_NULL_OK}, + {BLT_CONFIG_COLOR, "-activeforeground", "activeForeground", + "ActiveForeground", DEF_AXIS_ACTIVEFOREGROUND, + Blt_Offset(Axis, activeFgColor), ALL_GRAPHS}, + {BLT_CONFIG_RELIEF, "-activerelief", "activeRelief", "Relief", + DEF_AXIS_ACTIVERELIEF, Blt_Offset(Axis, activeRelief), + ALL_GRAPHS | BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_DOUBLE, "-autorange", "autoRange", "AutoRange", + DEF_AXIS_RANGE, Blt_Offset(Axis, windowSize), + ALL_GRAPHS | BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_BACKGROUND, "-background", "background", "Background", + DEF_AXIS_BACKGROUND, Blt_Offset(Axis, normalBg), + ALL_GRAPHS | BLT_CONFIG_NULL_OK}, + {BLT_CONFIG_SYNONYM, "-bg", "background", (char *)NULL, (char *)NULL, 0, 0}, + {BLT_CONFIG_LIST, "-bindtags", "bindTags", "BindTags", DEF_AXIS_TAGS, + Blt_Offset(Axis, obj.tags), ALL_GRAPHS | BLT_CONFIG_NULL_OK}, + {BLT_CONFIG_SYNONYM, "-bd", "borderWidth", (char *)NULL, (char *)NULL, + 0, ALL_GRAPHS}, + {BLT_CONFIG_PIXELS_NNEG, "-borderwidth", "borderWidth", "BorderWidth", + DEF_AXIS_BORDERWIDTH, Blt_Offset(Axis, borderWidth), + ALL_GRAPHS | BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_BITMASK, "-checklimits", "checkLimits", "CheckLimits", + DEF_AXIS_CHECKLIMITS, Blt_Offset(Axis, flags), + ALL_GRAPHS | BLT_CONFIG_DONT_SET_DEFAULT, + (Blt_CustomOption *)AXIS_CHECK_LIMITS}, + {BLT_CONFIG_COLOR, "-color", "color", "Color", + DEF_AXIS_FOREGROUND, Blt_Offset(Axis, tickColor), ALL_GRAPHS}, + {BLT_CONFIG_STRING, "-command", "command", "Command", + DEF_AXIS_COMMAND, Blt_Offset(Axis, formatCmd), + BLT_CONFIG_NULL_OK | ALL_GRAPHS}, + {BLT_CONFIG_BOOLEAN, "-descending", "descending", "Descending", + DEF_AXIS_DESCENDING, Blt_Offset(Axis, descending), + ALL_GRAPHS | BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_BITMASK, "-exterior", "exterior", "exterior", DEF_AXIS_EXTERIOR, + Blt_Offset(Axis, flags), ALL_GRAPHS | BLT_CONFIG_DONT_SET_DEFAULT, + (Blt_CustomOption *)AXIS_EXTERIOR}, + {BLT_CONFIG_SYNONYM, "-fg", "color", (char *)NULL, + (char *)NULL, 0, ALL_GRAPHS}, + {BLT_CONFIG_SYNONYM, "-foreground", "color", (char *)NULL, + (char *)NULL, 0, ALL_GRAPHS}, + {BLT_CONFIG_BITMASK, "-grid", "grid", "Grid", DEF_AXIS_GRID_BARCHART, + Blt_Offset(Axis, flags), BARCHART, (Blt_CustomOption *)AXIS_GRID}, + {BLT_CONFIG_BITMASK, "-grid", "grid", "Grid", DEF_AXIS_GRID_GRAPH, + Blt_Offset(Axis, flags), GRAPH | STRIPCHART, + (Blt_CustomOption *)AXIS_GRID}, + {BLT_CONFIG_COLOR, "-gridcolor", "gridColor", "GridColor", + DEF_AXIS_GRIDCOLOR, Blt_Offset(Axis, major.color), ALL_GRAPHS}, + {BLT_CONFIG_DASHES, "-griddashes", "gridDashes", "GridDashes", + DEF_AXIS_GRIDDASHES, Blt_Offset(Axis, major.dashes), + BLT_CONFIG_NULL_OK | ALL_GRAPHS}, + {BLT_CONFIG_PIXELS_NNEG, "-gridlinewidth", "gridLineWidth", + "GridLineWidth", DEF_AXIS_GRIDLINEWIDTH, + Blt_Offset(Axis, major.lineWidth), + BLT_CONFIG_DONT_SET_DEFAULT | ALL_GRAPHS}, + {BLT_CONFIG_BITMASK, "-gridminor", "gridMinor", "GridMinor", + DEF_AXIS_GRIDMINOR, Blt_Offset(Axis, flags), + BLT_CONFIG_DONT_SET_DEFAULT | ALL_GRAPHS, + (Blt_CustomOption *)AXIS_GRIDMINOR}, + {BLT_CONFIG_COLOR, "-gridminorcolor", "gridMinorColor", "GridColor", + DEF_AXIS_GRIDMINOR_COLOR, Blt_Offset(Axis, minor.color), ALL_GRAPHS}, + {BLT_CONFIG_DASHES, "-gridminordashes", "gridMinorDashes", "GridDashes", + DEF_AXIS_GRIDDASHES, Blt_Offset(Axis, minor.dashes), + BLT_CONFIG_NULL_OK | ALL_GRAPHS}, + {BLT_CONFIG_PIXELS_NNEG, "-gridminorlinewidth", "gridMinorLineWidth", + "GridLineWidth", DEF_AXIS_GRIDLINEWIDTH, + Blt_Offset(Axis, minor.lineWidth), + BLT_CONFIG_DONT_SET_DEFAULT | ALL_GRAPHS}, + {BLT_CONFIG_BITMASK, "-hide", "hide", "Hide", DEF_AXIS_HIDE, + Blt_Offset(Axis, flags), ALL_GRAPHS | BLT_CONFIG_DONT_SET_DEFAULT, + (Blt_CustomOption *)HIDE}, + {BLT_CONFIG_JUSTIFY, "-justify", "justify", "Justify", + DEF_AXIS_JUSTIFY, Blt_Offset(Axis, titleJustify), + ALL_GRAPHS | BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_BOOLEAN, "-labeloffset", "labelOffset", "LabelOffset", + (char *)NULL, Blt_Offset(Axis, labelOffset), ALL_GRAPHS}, + {BLT_CONFIG_COLOR, "-limitscolor", "limitsColor", "Color", + DEF_AXIS_FOREGROUND, Blt_Offset(Axis, limitsTextStyle.color), + ALL_GRAPHS}, + {BLT_CONFIG_FONT, "-limitsfont", "limitsFont", "Font", DEF_AXIS_LIMITS_FONT, + Blt_Offset(Axis, limitsTextStyle.font), ALL_GRAPHS}, + {BLT_CONFIG_CUSTOM, "-limitsformat", "limitsFormat", "LimitsFormat", + (char *)NULL, Blt_Offset(Axis, limitsFormats), + BLT_CONFIG_NULL_OK | ALL_GRAPHS, &formatOption}, + {BLT_CONFIG_PIXELS_NNEG, "-linewidth", "lineWidth", "LineWidth", + DEF_AXIS_LINEWIDTH, Blt_Offset(Axis, lineWidth), + ALL_GRAPHS | BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_BOOLEAN, "-logscale", "logScale", "LogScale", + DEF_AXIS_LOGSCALE, Blt_Offset(Axis, logScale), + ALL_GRAPHS | BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_CUSTOM, "-loose", "loose", "Loose", DEF_AXIS_LOOSE, 0, + ALL_GRAPHS | BLT_CONFIG_DONT_SET_DEFAULT, &looseOption}, + {BLT_CONFIG_CUSTOM, "-majorticks", "majorTicks", "MajorTicks", + (char *)NULL, Blt_Offset(Axis, t1Ptr), + BLT_CONFIG_NULL_OK | ALL_GRAPHS, &majorTicksOption}, + {BLT_CONFIG_CUSTOM, "-max", "max", "Max", (char *)NULL, + Blt_Offset(Axis, reqMax), ALL_GRAPHS, &limitOption}, + {BLT_CONFIG_CUSTOM, "-min", "min", "Min", (char *)NULL, + Blt_Offset(Axis, reqMin), ALL_GRAPHS, &limitOption}, + {BLT_CONFIG_CUSTOM, "-minorticks", "minorTicks", "MinorTicks", + (char *)NULL, Blt_Offset(Axis, t2Ptr), + BLT_CONFIG_NULL_OK | ALL_GRAPHS, &minorTicksOption}, + {BLT_CONFIG_RELIEF, "-relief", "relief", "Relief", + DEF_AXIS_RELIEF, Blt_Offset(Axis, relief), + ALL_GRAPHS | BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_FLOAT, "-rotate", "rotate", "Rotate", DEF_AXIS_ANGLE, + Blt_Offset(Axis, tickAngle), ALL_GRAPHS | BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_OBJ, "-scrollcommand", "scrollCommand", "ScrollCommand", + (char *)NULL, Blt_Offset(Axis, scrollCmdObjPtr), + ALL_GRAPHS | BLT_CONFIG_NULL_OK}, + {BLT_CONFIG_PIXELS_POS, "-scrollincrement", "scrollIncrement", + "ScrollIncrement", DEF_AXIS_SCROLL_INCREMENT, + Blt_Offset(Axis, scrollUnits), ALL_GRAPHS|BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_CUSTOM, "-scrollmax", "scrollMax", "ScrollMax", (char *)NULL, + Blt_Offset(Axis, reqScrollMax), ALL_GRAPHS, &limitOption}, + {BLT_CONFIG_CUSTOM, "-scrollmin", "scrollMin", "ScrollMin", (char *)NULL, + Blt_Offset(Axis, reqScrollMin), ALL_GRAPHS, &limitOption}, + {BLT_CONFIG_DOUBLE, "-shiftby", "shiftBy", "ShiftBy", + DEF_AXIS_SHIFTBY, Blt_Offset(Axis, shiftBy), + ALL_GRAPHS | BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_BITMASK, "-showticks", "showTicks", "ShowTicks", + DEF_AXIS_SHOWTICKS, Blt_Offset(Axis, flags), + ALL_GRAPHS | BLT_CONFIG_DONT_SET_DEFAULT, + (Blt_CustomOption *)AXIS_SHOWTICKS}, + {BLT_CONFIG_DOUBLE, "-stepsize", "stepSize", "StepSize", + DEF_AXIS_STEP, Blt_Offset(Axis, reqStep), + ALL_GRAPHS | BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_INT, "-subdivisions", "subdivisions", "Subdivisions", + DEF_AXIS_SUBDIVISIONS, Blt_Offset(Axis, reqNumMinorTicks), ALL_GRAPHS}, + {BLT_CONFIG_ANCHOR, "-tickanchor", "tickAnchor", "Anchor", + DEF_AXIS_TICK_ANCHOR, Blt_Offset(Axis, reqTickAnchor), ALL_GRAPHS}, + {BLT_CONFIG_FONT, "-tickfont", "tickFont", "Font", + DEF_AXIS_TICKFONT_GRAPH, Blt_Offset(Axis, tickFont), + GRAPH | STRIPCHART}, + {BLT_CONFIG_FONT, "-tickfont", "tickFont", "Font", + DEF_AXIS_TICKFONT_BARCHART, Blt_Offset(Axis, tickFont), BARCHART}, + {BLT_CONFIG_PIXELS_NNEG, "-ticklength", "tickLength", "TickLength", + DEF_AXIS_TICKLENGTH, Blt_Offset(Axis, tickLength), + ALL_GRAPHS | BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_INT, "-tickdefault", "tickDefault", "TickDefault", + DEF_AXIS_DIVISIONS, Blt_Offset(Axis, reqNumMajorTicks), + ALL_GRAPHS | BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_STRING, "-title", "title", "Title", + (char *)NULL, Blt_Offset(Axis, title), + BLT_CONFIG_DONT_SET_DEFAULT | BLT_CONFIG_NULL_OK | ALL_GRAPHS}, + {BLT_CONFIG_BOOLEAN, "-titlealternate", "titleAlternate", "TitleAlternate", + DEF_AXIS_TITLE_ALTERNATE, Blt_Offset(Axis, titleAlternate), + BLT_CONFIG_DONT_SET_DEFAULT | ALL_GRAPHS}, + {BLT_CONFIG_COLOR, "-titlecolor", "titleColor", "Color", + DEF_AXIS_FOREGROUND, Blt_Offset(Axis, titleColor), + ALL_GRAPHS}, + {BLT_CONFIG_FONT, "-titlefont", "titleFont", "Font", DEF_AXIS_TITLE_FONT, + Blt_Offset(Axis, titleFont), ALL_GRAPHS}, + {BLT_CONFIG_CUSTOM, "-use", "use", "Use", (char *)NULL, 0, ALL_GRAPHS, + &useOption}, + {BLT_CONFIG_END, NULL, NULL, NULL, NULL, 0, 0} +}; + +/* Forward declarations */ +static void DestroyAxis(Axis *axisPtr); +static Tcl_FreeProc FreeAxis; +static int GetAxisByClass(Tcl_Interp *interp, Graph *graphPtr, Tcl_Obj *objPtr, + ClassId classId, Axis **axisPtrPtr); +static void TimeScaleAxis(Axis *axisPtr, double min, double max); + +static int lastMargin; +typedef int (GraphAxisProc)(Tcl_Interp *interp, Axis *axisPtr, int objc, + Tcl_Obj *const *objv); +typedef int (GraphVirtualAxisProc)(Tcl_Interp *interp, Graph *graphPtr, + int objc, Tcl_Obj *const *objv); + +INLINE static double +Clamp(double x) +{ + return (x < 0.0) ? 0.0 : (x > 1.0) ? 1.0 : x; +} + +INLINE static int +Round(double x) +{ + return (int) (x + ((x < 0.0) ? -0.5 : 0.5)); +} + +static void +SetAxisRange(AxisRange *rangePtr, double min, double max) +{ + rangePtr->min = min; + rangePtr->max = max; + rangePtr->range = max - min; + if (FABS(rangePtr->range) < DBL_EPSILON) { + rangePtr->range = 1.0; + } + rangePtr->scale = 1.0 / rangePtr->range; +} + +/* + *--------------------------------------------------------------------------- + * + * InRange -- + * + * Determines if a value lies within a given range. + * + * The value is normalized and compared against the interval [0..1], + * where 0.0 is the minimum and 1.0 is the maximum. DBL_EPSILON is the + * smallest number that can be represented on the host machine, such that + * (1.0 + epsilon) != 1.0. + * + * Please note, *max* can't equal *min*. + * + * Results: + * If the value is within the interval [min..max], 1 is returned; 0 + * otherwise. + * + *--------------------------------------------------------------------------- + */ +INLINE static int +InRange(double x, AxisRange *rangePtr) +{ + if (rangePtr->range < DBL_EPSILON) { + return (FABS(rangePtr->max - x) >= DBL_EPSILON); + } else { + double norm; + + norm = (x - rangePtr->min) * rangePtr->scale; + return ((norm >= -DBL_EPSILON) && ((norm - 1.0) < DBL_EPSILON)); + } +} + +INLINE static int +AxisIsHorizontal(Axis *axisPtr) +{ + Graph *graphPtr = axisPtr->obj.graphPtr; + + return ((axisPtr->obj.classId == CID_AXIS_Y) == graphPtr->inverted); +} + + +static void +ReleaseAxis(Axis *axisPtr) +{ + if (axisPtr != NULL) { + axisPtr->refCount--; + assert(axisPtr->refCount >= 0); + if (axisPtr->refCount == 0) { + axisPtr->flags |= DELETE_PENDING; + Tcl_EventuallyFree(axisPtr, FreeAxis); + } + } +} + +/* + *----------------------------------------------------------------------------- + * Custom option parse and print procedures + *----------------------------------------------------------------------------- + */ + +/*ARGSUSED*/ +static void +FreeAxisProc( + ClientData clientData, /* Not used. */ + Display *display, /* Not used. */ + char *widgRec, + int offset) +{ + Axis **axisPtrPtr = (Axis **)(widgRec + offset); + + if (*axisPtrPtr != NULL) { + ReleaseAxis(*axisPtrPtr); + *axisPtrPtr = NULL; + } +} + +/* + *--------------------------------------------------------------------------- + * + * ObjToAxisProc -- + * + * Converts the name of an axis to a pointer to its axis structure. + * + * Results: + * The return value is a standard TCL result. The axis flags are written + * into the widget record. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +ObjToAxisProc( + ClientData clientData, /* Class identifier of the type of + * axis we are looking for. */ + Tcl_Interp *interp, /* Interpreter to report results. */ + Tk_Window tkwin, /* Used to look up pointer to graph. */ + Tcl_Obj *objPtr, /* String representing new value. */ + char *widgRec, /* Pointer to structure record. */ + int offset, /* Offset to field in structure */ + int flags) +{ + ClassId classId = (ClassId)clientData; + Axis **axisPtrPtr = (Axis **)(widgRec + offset); + Axis *axisPtr; + Graph *graphPtr; + + if (flags & BLT_CONFIG_NULL_OK) { + const char *string; + + string = Tcl_GetString(objPtr); + if (string[0] == '\0') { + ReleaseAxis(*axisPtrPtr); + *axisPtrPtr = NULL; + return TCL_OK; + } + } + graphPtr = Blt_GetGraphFromWindowData(tkwin); + assert(graphPtr); + if (GetAxisByClass(interp, graphPtr, objPtr, classId, &axisPtr) + != TCL_OK) { + return TCL_ERROR; + } + ReleaseAxis(*axisPtrPtr); + *axisPtrPtr = axisPtr; + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * AxisToObjProc -- + * + * Convert the window coordinates into a string. + * + * Results: + * The string representing the coordinate position is returned. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static Tcl_Obj * +AxisToObjProc( + ClientData clientData, /* Not used. */ + Tcl_Interp *interp, /* Not used. */ + Tk_Window tkwin, /* Not used. */ + char *widgRec, /* Pointer to structure record .*/ + int offset, /* Offset to field in structure */ + int flags) /* Not used. */ +{ + Axis *axisPtr = *(Axis **)(widgRec + offset); + const char *name; + + name = (axisPtr == NULL) ? "" : axisPtr->obj.name; + return Tcl_NewStringObj(name, -1); +} + +/*ARGSUSED*/ +static void +FreeFormatProc( + ClientData clientData, /* Not used. */ + Display *display, /* Not used. */ + char *widgRec, + int offset) /* Not used. */ +{ + Axis *axisPtr = (Axis *)(widgRec); + + if (axisPtr->limitsFormats != NULL) { + Blt_Free(axisPtr->limitsFormats); + axisPtr->limitsFormats = NULL; + } + axisPtr->nFormats = 0; +} + + +/* + *--------------------------------------------------------------------------- + * + * ObjToFormatProc -- + * + * Convert the name of virtual axis to an pointer. + * + * Results: + * The return value is a standard TCL result. The axis flags are written + * into the widget record. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +ObjToFormatProc( + ClientData clientData, /* Not used. */ + Tcl_Interp *interp, /* Interpreter to report results. */ + Tk_Window tkwin, /* Not used. */ + Tcl_Obj *objPtr, /* String representing new value. */ + char *widgRec, /* Pointer to structure record. */ + int offset, /* Not used. */ + int flags) /* Not used. */ +{ + Axis *axisPtr = (Axis *)(widgRec); + const char **argv; + int argc; + + if (Tcl_SplitList(interp, Tcl_GetString(objPtr), &argc, &argv) != TCL_OK) { + return TCL_ERROR; + } + if (argc > 2) { + Tcl_AppendResult(interp, "too many elements in limits format list \"", + Tcl_GetString(objPtr), "\"", (char *)NULL); + Blt_Free(argv); + return TCL_ERROR; + } + if (axisPtr->limitsFormats != NULL) { + Blt_Free(axisPtr->limitsFormats); + } + axisPtr->limitsFormats = argv; + axisPtr->nFormats = argc; + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * FormatToObjProc -- + * + * Convert the window coordinates into a string. + * + * Results: + * The string representing the coordinate position is returned. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static Tcl_Obj * +FormatToObjProc( + ClientData clientData, /* Not used. */ + Tcl_Interp *interp, /* Not used. */ + Tk_Window tkwin, /* Not used. */ + char *widgRec, /* Widget record */ + int offset, /* Not used. */ + int flags) /* Not used. */ +{ + Axis *axisPtr = (Axis *)(widgRec); + Tcl_Obj *objPtr; + + if (axisPtr->nFormats == 0) { + objPtr = Tcl_NewStringObj("", -1); + } else { + const char *string; + + string = Tcl_Merge(axisPtr->nFormats, axisPtr->limitsFormats); + objPtr = Tcl_NewStringObj(string, -1); + Blt_Free(string); + } + return objPtr; +} + +/* + *--------------------------------------------------------------------------- + * + * ObjToLimitProc -- + * + * Convert the string representation of an axis limit into its numeric + * form. + * + * Results: + * The return value is a standard TCL result. The symbol type is written + * into the widget record. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +ObjToLimitProc( + ClientData clientData, /* Not used. */ + Tcl_Interp *interp, /* Interpreter to send results back + * to */ + Tk_Window tkwin, /* Not used. */ + Tcl_Obj *objPtr, /* String representing new value. */ + char *widgRec, /* Pointer to structure record. */ + int offset, /* Offset to field in structure */ + int flags) /* Not used. */ +{ + double *limitPtr = (double *)(widgRec + offset); + const char *string; + + string = Tcl_GetString(objPtr); + if (string[0] == '\0') { + *limitPtr = Blt_NaN(); + } else if (Blt_ExprDoubleFromObj(interp, objPtr, limitPtr) != TCL_OK) { + return TCL_ERROR; + } + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * LimitToObjProc -- + * + * Convert the floating point axis limits into a string. + * + * Results: + * The string representation of the limits is returned. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static Tcl_Obj * +LimitToObjProc( + ClientData clientData, /* Not used. */ + Tcl_Interp *interp, /* Not used. */ + Tk_Window tkwin, /* Not used. */ + char *widgRec, /* */ + int offset, /* Offset to field in structure */ + int flags) /* Not used. */ +{ + double limit = *(double *)(widgRec + offset); + Tcl_Obj *objPtr; + + if (DEFINED(limit)) { + objPtr = Tcl_NewDoubleObj(limit); + } else { + objPtr = Tcl_NewStringObj("", -1); + } + return objPtr; +} + +/* + *--------------------------------------------------------------------------- + * + * ObjToUseProc -- + * + * Convert the string representation of the margin to use into its + * numeric form. + * + * Results: + * The return value is a standard TCL result. The use type is written + * into the widget record. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +ObjToUseProc( + ClientData clientData, /* Not used. */ + Tcl_Interp *interp, /* Interpreter to send results. */ + Tk_Window tkwin, /* Not used. */ + Tcl_Obj *objPtr, /* String representing new value. */ + char *widgRec, /* Pointer to structure record. */ + int offset, /* Offset to field in structure */ + int flags) /* Not used. */ +{ + Axis *axisPtr = (Axis *)(widgRec); + AxisName *p, *pend; + Blt_Chain chain; + Graph *graphPtr; + const char *string; + int margin; + + graphPtr = axisPtr->obj.graphPtr; + if (axisPtr->refCount == 0) { + /* Clear the axis class if it's not currently used by an element.*/ + Blt_GraphSetObjectClass(&axisPtr->obj, CID_NONE); + } + /* Remove the axis from the margin's use list and clear its use flag. */ + if (axisPtr->link != NULL) { + Blt_Chain_UnlinkLink(axisPtr->chain, axisPtr->link); + } + axisPtr->flags &= ~AXIS_USE; + string = Tcl_GetString(objPtr); + if ((string == NULL) || (string[0] == '\0')) { + goto done; + } + for (p = axisNames, pend = axisNames + nAxisNames; p < pend; p++) { + if (strcmp(p->name, string) == 0) { + break; /* Found the axis name. */ + } + } + if (p == pend) { + Tcl_AppendResult(interp, "unknown axis type \"", string, "\": " + "should be x, y, x1, y2, or \"\".", (char *)NULL); + return TCL_ERROR; + } + /* Check the axis class. Can't use the axis if it's already being used as + * another type. */ + if (axisPtr->obj.classId == CID_NONE) { + Blt_GraphSetObjectClass(&axisPtr->obj, p->classId); + } else if (axisPtr->obj.classId != p->classId) { + Tcl_AppendResult(interp, "wrong type for axis \"", + axisPtr->obj.name, "\": can't use ", + axisPtr->obj.className, " type axis.", (char *)NULL); + return TCL_ERROR; + } + margin = (graphPtr->inverted) ? p->invertMargin : p->margin; + chain = graphPtr->margins[margin].axes; + if (axisPtr->link != NULL) { + /* Move the axis from the old margin's "use" list to the new. */ + Blt_Chain_AppendLink(chain, axisPtr->link); + } else { + axisPtr->link = Blt_Chain_Append(chain, axisPtr); + } + axisPtr->chain = chain; + axisPtr->flags |= AXIS_USE; + axisPtr->margin = margin; + done: + graphPtr->flags |= (GET_AXIS_GEOMETRY | LAYOUT_NEEDED | RESET_AXES); + /* When any axis changes, we need to layout the entire graph. */ + graphPtr->flags |= (MAP_WORLD | REDRAW_WORLD); + Blt_EventuallyRedrawGraph(graphPtr); + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * UseToObjProc -- + * + * Convert the floating point axis limits into a string. + * + * Results: + * The string representation of the limits is returned. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static Tcl_Obj * +UseToObjProc( + ClientData clientData, /* Not used. */ + Tcl_Interp *interp, /* Not used. */ + Tk_Window tkwin, /* Not used. */ + char *widgRec, /* */ + int offset, /* Offset to field in structure */ + int flags) /* Not used. */ +{ + Axis *axisPtr = (Axis *)(widgRec); + + if (axisPtr->margin == MARGIN_NONE) { + return Tcl_NewStringObj("", -1); + } + return Tcl_NewStringObj(axisNames[axisPtr->margin].name, -1); +} + +/*ARGSUSED*/ +static void +FreeTicksProc( + ClientData clientData, /* Either AXIS_AUTO_MAJOR or + * AXIS_AUTO_MINOR. */ + Display *display, /* Not used. */ + char *widgRec, + int offset) +{ + Axis *axisPtr = (Axis *)widgRec; + Ticks **ticksPtrPtr = (Ticks **) (widgRec + offset); + unsigned long mask = (unsigned long)clientData; + + axisPtr->flags |= mask; + if (*ticksPtrPtr != NULL) { + Blt_Free(*ticksPtrPtr); + } + *ticksPtrPtr = NULL; +} + +/* + *--------------------------------------------------------------------------- + * + * ObjToTicksProc -- + * + * + * Results: + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +ObjToTicksProc( + ClientData clientData, /* Either AXIS_AUTO_MAJOR or + * AXIS_AUTO_MINOR. */ + Tcl_Interp *interp, /* Interpreter to send results. */ + Tk_Window tkwin, /* Not used. */ + Tcl_Obj *objPtr, /* String representing new value. */ + char *widgRec, /* Pointer to structure record. */ + int offset, /* Offset to field in structure */ + int flags) /* Not used. */ +{ + Axis *axisPtr = (Axis *)widgRec; + Tcl_Obj **objv; + Ticks **ticksPtrPtr = (Ticks **) (widgRec + offset); + Ticks *ticksPtr; + int objc; + unsigned long mask = (unsigned long)clientData; + + if (Tcl_ListObjGetElements(interp, objPtr, &objc, &objv) != TCL_OK) { + return TCL_ERROR; + } + axisPtr->flags |= mask; + ticksPtr = NULL; + if (objc > 0) { + int i; + + ticksPtr = Blt_AssertMalloc(sizeof(Ticks) + (objc*sizeof(double))); + for (i = 0; i < objc; i++) { + double value; + + if (Blt_ExprDoubleFromObj(interp, objv[i], &value) != TCL_OK) { + Blt_Free(ticksPtr); + return TCL_ERROR; + } + ticksPtr->values[i] = value; + } + ticksPtr->nTicks = objc; + axisPtr->flags &= ~mask; + } + FreeTicksProc(clientData, Tk_Display(tkwin), widgRec, offset); + *ticksPtrPtr = ticksPtr; + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * TicksToObjProc -- + * + * Convert array of tick coordinates to a list. + * + * Results: + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static Tcl_Obj * +TicksToObjProc( + ClientData clientData, /* Either AXIS_AUTO_MAJOR or + * AXIS_AUTO_MINOR. */ + Tcl_Interp *interp, + Tk_Window tkwin, /* Not used. */ + char *widgRec, /* */ + int offset, /* Offset to field in structure */ + int flags) /* Not used. */ +{ + Axis *axisPtr; + Tcl_Obj *listObjPtr; + Ticks *ticksPtr; + unsigned long mask; + + axisPtr = (Axis *)widgRec; + ticksPtr = *(Ticks **) (widgRec + offset); + mask = (unsigned long)clientData; + listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL); + if ((ticksPtr != NULL) && ((axisPtr->flags & mask) == 0)) { + unsigned int i; + + for (i = 0; i < ticksPtr->nTicks; i++) { + Tcl_Obj *objPtr; + + objPtr = Tcl_NewDoubleObj(ticksPtr->values[i]); + Tcl_ListObjAppendElement(interp, listObjPtr, objPtr); + } + } + return listObjPtr; +} + +/* + *--------------------------------------------------------------------------- + * + * ObjToLooseProc -- + * + * Convert a string to one of three values. + * 0 - false, no, off + * 1 - true, yes, on + * 2 - always + * Results: + * If the string is successfully converted, TCL_OK is returned. + * Otherwise, TCL_ERROR is returned and an error message is left in + * interpreter's result field. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +ObjToLooseProc( + ClientData clientData, /* Not used. */ + Tcl_Interp *interp, /* Interpreter to send results. */ + Tk_Window tkwin, /* Not used. */ + Tcl_Obj *objPtr, /* String representing new value. */ + char *widgRec, /* Pointer to structure record. */ + int offset, /* Not used. */ + int flags) /* Not used. */ +{ + Axis *axisPtr = (Axis *)(widgRec); + Tcl_Obj **objv; + int i; + int objc; + int values[2]; + + if (Tcl_ListObjGetElements(interp, objPtr, &objc, &objv) != TCL_OK) { + return TCL_ERROR; + } + if ((objc < 1) || (objc > 2)) { + Tcl_AppendResult(interp, "wrong # elements in loose value \"", + Tcl_GetString(objPtr), "\"", (char *)NULL); + return TCL_ERROR; + } + for (i = 0; i < objc; i++) { + const char *string; + + string = Tcl_GetString(objv[i]); + if ((string[0] == 'a') && (strcmp(string, "always") == 0)) { + values[i] = AXIS_ALWAYS_LOOSE; + } else { + int bool; + + if (Tcl_GetBooleanFromObj(interp, objv[i], &bool) != TCL_OK) { + return TCL_ERROR; + } + values[i] = bool; + } + } + axisPtr->looseMin = axisPtr->looseMax = values[0]; + if (objc > 1) { + axisPtr->looseMax = values[1]; + } + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * LooseToObjProc -- + * + * Results: + * The string representation of the auto boolean is returned. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static Tcl_Obj * +LooseToObjProc( + ClientData clientData, /* Not used. */ + Tcl_Interp *interp, + Tk_Window tkwin, /* Not used. */ + char *widgRec, /* Widget record */ + int offset, /* Not used. */ + int flags) /* Not used. */ +{ + Axis *axisPtr = (Axis *)widgRec; + Tcl_Obj *listObjPtr; + + listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL); + if (axisPtr->looseMin == AXIS_TIGHT) { + Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewBooleanObj(FALSE)); + } else if (axisPtr->looseMin == AXIS_LOOSE) { + Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewBooleanObj(TRUE)); + } else if (axisPtr->looseMin == AXIS_ALWAYS_LOOSE) { + Tcl_ListObjAppendElement(interp, listObjPtr, + Tcl_NewStringObj("always", 6)); + } + if (axisPtr->looseMin != axisPtr->looseMax) { + if (axisPtr->looseMax == AXIS_TIGHT) { + Tcl_ListObjAppendElement(interp, listObjPtr, + Tcl_NewBooleanObj(FALSE)); + } else if (axisPtr->looseMax == AXIS_LOOSE) { + Tcl_ListObjAppendElement(interp, listObjPtr, + Tcl_NewBooleanObj(TRUE)); + } else if (axisPtr->looseMax == AXIS_ALWAYS_LOOSE) { + Tcl_ListObjAppendElement(interp, listObjPtr, + Tcl_NewStringObj("always", 6)); + } + } + return listObjPtr; +} + +static void +FreeTickLabels(Blt_Chain chain) +{ + Blt_ChainLink link; + + for (link = Blt_Chain_FirstLink(chain); link != NULL; + link = Blt_Chain_NextLink(link)) { + TickLabel *labelPtr; + + labelPtr = Blt_Chain_GetValue(link); + Blt_Free(labelPtr); + } + Blt_Chain_Reset(chain); +} + +/* + *--------------------------------------------------------------------------- + * + * MakeLabel -- + * + * Converts a floating point tick value to a string to be used as its + * label. + * + * Results: + * None. + * + * Side Effects: + * Returns a new label in the string character buffer. The formatted + * tick label will be displayed on the graph. + * + * -------------------------------------------------------------------------- + */ +static TickLabel * +MakeLabel(Axis *axisPtr, double value) +{ +#define TICK_LABEL_SIZE 200 + char string[TICK_LABEL_SIZE + 1]; + TickLabel *labelPtr; + + /* Generate a default tick label based upon the tick value. */ + if (axisPtr->logScale) { + sprintf_s(string, TICK_LABEL_SIZE, "1E%d", ROUND(value)); + } else { + sprintf_s(string, TICK_LABEL_SIZE, "%.*G", NUMDIGITS, value); + } + + if (axisPtr->formatCmd != NULL) { + Graph *graphPtr; + Tcl_Interp *interp; + Tk_Window tkwin; + + graphPtr = axisPtr->obj.graphPtr; + interp = graphPtr->interp; + tkwin = graphPtr->tkwin; + /* + * A TCL proc was designated to format tick labels. Append the path + * name of the widget and the default tick label as arguments when + * invoking it. Copy and save the new label from interp->result. + */ + Tcl_ResetResult(interp); + if (Tcl_VarEval(interp, axisPtr->formatCmd, " ", Tk_PathName(tkwin), + " ", string, (char *)NULL) != TCL_OK) { + Tcl_BackgroundError(interp); + } else { + /* + * The proc could return a string of any length, so arbitrarily + * limit it to what will fit in the return string. + */ + strncpy(string, Tcl_GetStringResult(interp), TICK_LABEL_SIZE); + string[TICK_LABEL_SIZE] = '\0'; + + Tcl_ResetResult(interp); /* Clear the interpreter's result. */ + } + } + labelPtr = Blt_AssertMalloc(sizeof(TickLabel) + strlen(string)); + strcpy(labelPtr->string, string); + labelPtr->anchorPos.x = labelPtr->anchorPos.y = DBL_MAX; + return labelPtr; +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_InvHMap -- + * + * Maps the given screen coordinate back to a graph coordinate. Called + * by the graph locater routine. + * + * Results: + * Returns the graph coordinate value at the given window + * y-coordinate. + * + *--------------------------------------------------------------------------- + */ +double +Blt_InvHMap(Axis *axisPtr, double x) +{ + double value; + + x = (double)(x - axisPtr->screenMin) * axisPtr->screenScale; + if (axisPtr->descending) { + x = 1.0 - x; + } + value = (x * axisPtr->axisRange.range) + axisPtr->axisRange.min; + if (axisPtr->logScale) { + value = EXP10(value); + } + return value; +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_InvVMap -- + * + * Maps the given screen y-coordinate back to the graph coordinate + * value. Called by the graph locater routine. + * + * Results: + * Returns the graph coordinate value for the given screen + * coordinate. + * + *--------------------------------------------------------------------------- + */ +double +Blt_InvVMap(Axis *axisPtr, double y) /* Screen coordinate */ +{ + double value; + + y = (double)(y - axisPtr->screenMin) * axisPtr->screenScale; + if (axisPtr->descending) { + y = 1.0 - y; + } + value = ((1.0 - y) * axisPtr->axisRange.range) + axisPtr->axisRange.min; + if (axisPtr->logScale) { + value = EXP10(value); + } + return value; +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_HMap -- + * + * Map the given graph coordinate value to its axis, returning a window + * position. + * + * Results: + * Returns a double precision number representing the window coordinate + * position on the given axis. + * + *--------------------------------------------------------------------------- + */ +double +Blt_HMap(Axis *axisPtr, double x) +{ + if ((axisPtr->logScale) && (x != 0.0)) { + x = log10(FABS(x)); + } + /* Map graph coordinate to normalized coordinates [0..1] */ + x = (x - axisPtr->axisRange.min) * axisPtr->axisRange.scale; + if (axisPtr->descending) { + x = 1.0 - x; + } + return (x * axisPtr->screenRange + axisPtr->screenMin); +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_VMap -- + * + * Map the given graph coordinate value to its axis, returning a window + * position. + * + * Results: + * Returns a double precision number representing the window coordinate + * position on the given axis. + * + *--------------------------------------------------------------------------- + */ +double +Blt_VMap(Axis *axisPtr, double y) +{ + if ((axisPtr->logScale) && (y != 0.0)) { + y = log10(FABS(y)); + } + /* Map graph coordinate to normalized coordinates [0..1] */ + y = (y - axisPtr->axisRange.min) * axisPtr->axisRange.scale; + if (axisPtr->descending) { + y = 1.0 - y; + } + return ((1.0 - y) * axisPtr->screenRange + axisPtr->screenMin); +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_Map2D -- + * + * Maps the given graph x,y coordinate values to a window position. + * + * Results: + * Returns a XPoint structure containing the window coordinates of + * the given graph x,y coordinate. + * + *--------------------------------------------------------------------------- + */ +Point2d +Blt_Map2D( + Graph *graphPtr, + double x, double y, /* Graph x and y coordinates */ + Axis2d *axesPtr) /* Specifies which axes to use */ +{ + Point2d point; + + if (graphPtr->inverted) { + point.x = Blt_HMap(axesPtr->y, y); + point.y = Blt_VMap(axesPtr->x, x); + } else { + point.x = Blt_HMap(axesPtr->x, x); + point.y = Blt_VMap(axesPtr->y, y); + } + return point; +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_InvMap2D -- + * + * Maps the given window x,y coordinates to graph values. + * + * Results: + * Returns a structure containing the graph coordinates of the given + * window x,y coordinate. + * + *--------------------------------------------------------------------------- + */ +Point2d +Blt_InvMap2D( + Graph *graphPtr, + double x, double y, /* Window x and y coordinates */ + Axis2d *axesPtr) /* Specifies which axes to use */ +{ + Point2d point; + + if (graphPtr->inverted) { + point.x = Blt_InvVMap(axesPtr->x, y); + point.y = Blt_InvHMap(axesPtr->y, x); + } else { + point.x = Blt_InvHMap(axesPtr->x, x); + point.y = Blt_InvVMap(axesPtr->y, y); + } + return point; +} + + +static void +GetDataLimits(Axis *axisPtr, double min, double max) +{ + if (axisPtr->valueRange.min > min) { + axisPtr->valueRange.min = min; + } + if (axisPtr->valueRange.max < max) { + axisPtr->valueRange.max = max; + } +} + +static void +FixAxisRange(Axis *axisPtr) +{ + double min, max; + + /* + * When auto-scaling, the axis limits are the bounds of the element data. + * If no data exists, set arbitrary limits (wrt to log/linear scale). + */ + min = axisPtr->valueRange.min; + max = axisPtr->valueRange.max; + + /* Check the requested axis limits. Can't allow -min to be greater + * than -max, or have undefined log scale limits. */ + if (((DEFINED(axisPtr->reqMin)) && (DEFINED(axisPtr->reqMax))) && + (axisPtr->reqMin >= axisPtr->reqMax)) { + axisPtr->reqMin = axisPtr->reqMax = Blt_NaN(); + } + if (axisPtr->logScale) { + if ((DEFINED(axisPtr->reqMin)) && (axisPtr->reqMin <= 0.0)) { + axisPtr->reqMin = Blt_NaN(); + } + if ((DEFINED(axisPtr->reqMax)) && (axisPtr->reqMax <= 0.0)) { + axisPtr->reqMax = Blt_NaN(); + } + } + + if (min == DBL_MAX) { + if (DEFINED(axisPtr->reqMin)) { + min = axisPtr->reqMin; + } else { + min = (axisPtr->logScale) ? 0.001 : 0.0; + } + } + if (max == -DBL_MAX) { + if (DEFINED(axisPtr->reqMax)) { + max = axisPtr->reqMax; + } else { + max = 1.0; + } + } + if (min >= max) { + /* + * There is no range of data (i.e. min is not less than max), so + * manufacture one. + */ + if (min == 0.0) { + min = 0.0, max = 1.0; + } else { + max = min + (FABS(min) * 0.1); + } + } + SetAxisRange(&axisPtr->valueRange, min, max); + + /* + * The axis limits are either the current data range or overridden by the + * values selected by the user with the -min or -max options. + */ + axisPtr->min = min; + axisPtr->max = max; + if (DEFINED(axisPtr->reqMin)) { + axisPtr->min = axisPtr->reqMin; + } + if (DEFINED(axisPtr->reqMax)) { + axisPtr->max = axisPtr->reqMax; + } + if (axisPtr->max < axisPtr->min) { + /* + * If the limits still don't make sense, it's because one limit + * configuration option (-min or -max) was set and the other default + * (based upon the data) is too small or large. Remedy this by making + * up a new min or max from the user-defined limit. + */ + if (!DEFINED(axisPtr->reqMin)) { + axisPtr->min = axisPtr->max - (FABS(axisPtr->max) * 0.1); + } + if (!DEFINED(axisPtr->reqMax)) { + axisPtr->max = axisPtr->min + (FABS(axisPtr->max) * 0.1); + } + } + /* + * If a window size is defined, handle auto ranging by shifting the axis + * limits. + */ + if ((axisPtr->windowSize > 0.0) && + (!DEFINED(axisPtr->reqMin)) && (!DEFINED(axisPtr->reqMax))) { + if (axisPtr->shiftBy < 0.0) { + axisPtr->shiftBy = 0.0; + } + max = axisPtr->min + axisPtr->windowSize; + if (axisPtr->max >= max) { + if (axisPtr->shiftBy > 0.0) { + max = UCEIL(axisPtr->max, axisPtr->shiftBy); + } + axisPtr->min = max - axisPtr->windowSize; + } + axisPtr->max = max; + } + if ((axisPtr->max != axisPtr->prevMax) || + (axisPtr->min != axisPtr->prevMin)) { + /* Indicate if the axis limits have changed */ + axisPtr->flags |= DIRTY; + /* and save the previous minimum and maximum values */ + axisPtr->prevMin = axisPtr->min; + axisPtr->prevMax = axisPtr->max; + } +} + +/* + *--------------------------------------------------------------------------- + * + * NiceNum -- + * + * Reference: Paul Heckbert, "Nice Numbers for Graph Labels", + * Graphics Gems, pp 61-63. + * + * Finds a "nice" number approximately equal to x. + * + *--------------------------------------------------------------------------- + */ +static double +NiceNum(double x, int round) /* If non-zero, round. Otherwise take + * ceiling of value. */ +{ + double expt; /* Exponent of x */ + double frac; /* Fractional part of x */ + double nice; /* Nice, rounded fraction */ + + expt = floor(log10(x)); + frac = x / EXP10(expt); /* between 1 and 10 */ + if (round) { + if (frac < 1.5) { + nice = 1.0; + } else if (frac < 3.0) { + nice = 2.0; + } else if (frac < 7.0) { + nice = 5.0; + } else { + nice = 10.0; + } + } else { + if (frac <= 1.0) { + nice = 1.0; + } else if (frac <= 2.0) { + nice = 2.0; + } else if (frac <= 5.0) { + nice = 5.0; + } else { + nice = 10.0; + } + } + return nice * EXP10(expt); +} + +static Ticks * +GenerateTicks(TickSweep *sweepPtr) +{ + Ticks *ticksPtr; + + ticksPtr = Blt_AssertMalloc(sizeof(Ticks) + + (sweepPtr->nSteps * sizeof(double))); + ticksPtr->nTicks = 0; + + if (sweepPtr->step == 0.0) { + /* Hack: A zero step indicates to use log values. */ + int i; + /* Precomputed log10 values [1..10] */ + static double logTable[] = { + 0.0, + 0.301029995663981, + 0.477121254719662, + 0.602059991327962, + 0.698970004336019, + 0.778151250383644, + 0.845098040014257, + 0.903089986991944, + 0.954242509439325, + 1.0 + }; + for (i = 0; i < sweepPtr->nSteps; i++) { + ticksPtr->values[i] = logTable[i]; + } + } else { + double value; + int i; + + value = sweepPtr->initial; /* Start from smallest axis tick */ + for (i = 0; i < sweepPtr->nSteps; i++) { + value = UROUND(value, sweepPtr->step); + ticksPtr->values[i] = value; + value += sweepPtr->step; + } + } + ticksPtr->nTicks = sweepPtr->nSteps; + return ticksPtr; +} + +/* + *--------------------------------------------------------------------------- + * + * LogScaleAxis -- + * + * Determine the range and units of a log scaled axis. + * + * Unless the axis limits are specified, the axis is scaled + * automatically, where the smallest and largest major ticks encompass + * the range of actual data values. When an axis limit is specified, + * that value represents the smallest(min)/largest(max) value in the + * displayed range of values. + * + * Both manual and automatic scaling are affected by the step used. By + * default, the step is the largest power of ten to divide the range in + * more than one piece. + * + * Automatic scaling: + * Find the smallest number of units which contain the range of values. + * The minimum and maximum major tick values will be represent the + * range of values for the axis. This greatest number of major ticks + * possible is 10. + * + * Manual scaling: + * Make the minimum and maximum data values the represent the range of + * the values for the axis. The minimum and maximum major ticks will be + * inclusive of this range. This provides the largest area for plotting + * and the expected results when the axis min and max values have be set + * by the user (.e.g zooming). The maximum number of major ticks is 20. + * + * For log scale, there's the possibility that the minimum and + * maximum data values are the same magnitude. To represent the + * points properly, at least one full decade should be shown. + * However, if you zoom a log scale plot, the results should be + * predictable. Therefore, in that case, show only minor ticks. + * Lastly, there should be an appropriate way to handle numbers + * <=0. + * + * maxY + * | units = magnitude (of least significant digit) + * | high = largest unit tick < max axis value + * high _| low = smallest unit tick > min axis value + * | + * | range = high - low + * | # ticks = greatest factor of range/units + * _| + * U | + * n | + * i | + * t _| + * | + * | + * | + * low _| + * | + * |_minX________________maxX__ + * | | | | | + * minY low high + * minY + * + * + * numTicks = Number of ticks + * min = Minimum value of axis + * max = Maximum value of axis + * range = Range of values (max - min) + * + * If the number of decades is greater than ten, it is assumed + * that the full set of log-style ticks can't be drawn properly. + * + * Results: + * None + * + * -------------------------------------------------------------------------- */ +static void +LogScaleAxis(Axis *axisPtr, double min, double max) +{ + double range; + double tickMin, tickMax; + double majorStep, minorStep; + int nMajor, nMinor; + + nMajor = nMinor = 0; + /* Suppress compiler warnings. */ + majorStep = minorStep = 0.0; + tickMin = tickMax = Blt_NaN(); + if (min < max) { + min = (min != 0.0) ? log10(FABS(min)) : 0.0; + max = (max != 0.0) ? log10(FABS(max)) : 1.0; + + tickMin = floor(min); + tickMax = ceil(max); + range = tickMax - tickMin; + + if (range > 10) { + /* There are too many decades to display a major tick at every + * decade. Instead, treat the axis as a linear scale. */ + range = NiceNum(range, 0); + majorStep = NiceNum(range / axisPtr->reqNumMajorTicks, 1); + tickMin = UFLOOR(tickMin, majorStep); + tickMax = UCEIL(tickMax, majorStep); + nMajor = (int)((tickMax - tickMin) / majorStep) + 1; + minorStep = EXP10(floor(log10(majorStep))); + if (minorStep == majorStep) { + nMinor = 4, minorStep = 0.2; + } else { + nMinor = Round(majorStep / minorStep) - 1; + } + } else { + if (tickMin == tickMax) { + tickMax++; + } + majorStep = 1.0; + nMajor = (int)(tickMax - tickMin + 1); /* FIXME: Check this. */ + + minorStep = 0.0; /* This is a special hack to pass + * information to the GenerateTicks + * routine. An interval of 0.0 tells 1) + * this is a minor sweep and 2) the axis + * is log scale. */ + nMinor = 10; + } + if ((axisPtr->looseMin == AXIS_TIGHT) || + ((axisPtr->looseMin == AXIS_LOOSE) && + (DEFINED(axisPtr->reqMin)))) { + tickMin = min; + nMajor++; + } + if ((axisPtr->looseMax == AXIS_TIGHT) || + ((axisPtr->looseMax == AXIS_LOOSE) && + (DEFINED(axisPtr->reqMax)))) { + tickMax = max; + } + } + axisPtr->majorSweep.step = majorStep; + axisPtr->majorSweep.initial = floor(tickMin); + axisPtr->majorSweep.nSteps = nMajor; + axisPtr->minorSweep.initial = axisPtr->minorSweep.step = minorStep; + axisPtr->minorSweep.nSteps = nMinor; + + SetAxisRange(&axisPtr->axisRange, tickMin, tickMax); +} + +/* + *--------------------------------------------------------------------------- + * + * LinearScaleAxis -- + * + * Determine the units of a linear scaled axis. + * + * The axis limits are either the range of the data values mapped + * to the axis (autoscaled), or the values specified by the -min + * and -max options (manual). + * + * If autoscaled, the smallest and largest major ticks will + * encompass the range of data values. If the -loose option is + * selected, the next outer ticks are choosen. If tight, the + * ticks are at or inside of the data limits are used. + * + * If manually set, the ticks are at or inside the data limits + * are used. This makes sense for zooming. You want the + * selected range to represent the next limit, not something a + * bit bigger. + * + * Note: I added an "always" value to the -loose option to force + * the manually selected axes to be loose. It's probably + * not a good idea. + * + * maxY + * | units = magnitude (of least significant digit) + * | high = largest unit tick < max axis value + * high _| low = smallest unit tick > min axis value + * | + * | range = high - low + * | # ticks = greatest factor of range/units + * _| + * U | + * n | + * i | + * t _| + * | + * | + * | + * low _| + * | + * |_minX________________maxX__ + * | | | | | + * minY low high + * minY + * + * numTicks = Number of ticks + * min = Minimum value of axis + * max = Maximum value of axis + * range = Range of values (max - min) + * + * Results: + * None. + * + * Side Effects: + * The axis tick information is set. The actual tick values will + * be generated later. + * + *--------------------------------------------------------------------------- + */ +static void +LinearScaleAxis(Axis *axisPtr, double min, double max) +{ + double step; + double tickMin, tickMax; + double axisMin, axisMax; + unsigned int nTicks; + + nTicks = 0; + step = 1.0; + /* Suppress compiler warning. */ + axisMin = axisMax = tickMin = tickMax = Blt_NaN(); + if (min < max) { + double range; + + range = max - min; + /* Calculate the major tick stepping. */ + if (axisPtr->reqStep > 0.0) { + /* An interval was designated by the user. Keep scaling it until + * it fits comfortably within the current range of the axis. */ + step = axisPtr->reqStep; + while ((2 * step) >= range) { + step *= 0.5; + } + } else { + range = NiceNum(range, 0); + step = NiceNum(range / axisPtr->reqNumMajorTicks, 1); + } + + /* Find the outer tick values. Add 0.0 to prevent getting -0.0. */ + axisMin = tickMin = floor(min / step) * step + 0.0; + axisMax = tickMax = ceil(max / step) * step + 0.0; + + nTicks = Round((tickMax - tickMin) / step) + 1; + } + axisPtr->majorSweep.step = step; + axisPtr->majorSweep.initial = tickMin; + axisPtr->majorSweep.nSteps = nTicks; + + /* + * The limits of the axis are either the range of the data ("tight") or at + * the next outer tick interval ("loose"). The looseness or tightness has + * to do with how the axis fits the range of data values. This option is + * overridden when the user sets an axis limit (by either -min or -max + * option). The axis limit is always at the selected limit (otherwise we + * assume that user would have picked a different number). + */ + if ((axisPtr->looseMin == AXIS_TIGHT) || + ((axisPtr->looseMin == AXIS_LOOSE) && + (DEFINED(axisPtr->reqMin)))) { + axisMin = min; + } + if ((axisPtr->looseMax == AXIS_TIGHT) || + ((axisPtr->looseMax == AXIS_LOOSE) && + (DEFINED(axisPtr->reqMax)))) { + axisMax = max; + } + SetAxisRange(&axisPtr->axisRange, axisMin, axisMax); + + /* Now calculate the minor tick step and number. */ + + if ((axisPtr->reqNumMinorTicks > 0) && (axisPtr->flags & AXIS_AUTO_MAJOR)) { + nTicks = axisPtr->reqNumMinorTicks - 1; + step = 1.0 / (nTicks + 1); + } else { + nTicks = 0; /* No minor ticks. */ + step = 0.5; /* Don't set the minor tick interval to + * 0.0. It makes the GenerateTicks + * routine * create minor log-scale tick + * marks. */ + } + axisPtr->minorSweep.initial = axisPtr->minorSweep.step = step; + axisPtr->minorSweep.nSteps = nTicks; +} + + +static void +SweepTicks(Axis *axisPtr) +{ + if (axisPtr->flags & AXIS_AUTO_MAJOR) { + if (axisPtr->t1Ptr != NULL) { + Blt_Free(axisPtr->t1Ptr); + } + axisPtr->t1Ptr = GenerateTicks(&axisPtr->majorSweep); + } + if (axisPtr->flags & AXIS_AUTO_MINOR) { + if (axisPtr->t2Ptr != NULL) { + Blt_Free(axisPtr->t2Ptr); + } + axisPtr->t2Ptr = GenerateTicks(&axisPtr->minorSweep); + } +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_ResetAxes -- + * + * Results: + * None. + * + *--------------------------------------------------------------------------- + */ +void +Blt_ResetAxes(Graph *graphPtr) +{ + Blt_ChainLink link; + Blt_HashEntry *hPtr; + Blt_HashSearch cursor; + + /* FIXME: This should be called whenever the display list of + * elements change. Maybe yet another flag INIT_STACKS to + * indicate that the element display list has changed. + * Needs to be done before the axis limits are set. + */ + Blt_InitBarSetTable(graphPtr); + if ((graphPtr->mode == BARS_STACKED) && (graphPtr->nBarGroups > 0)) { + Blt_ComputeBarStacks(graphPtr); + } + /* + * Step 1: Reset all axes. Initialize the data limits of the axis to + * impossible values. + */ + for (hPtr = Blt_FirstHashEntry(&graphPtr->axes.table, &cursor); + hPtr != NULL; hPtr = Blt_NextHashEntry(&cursor)) { + Axis *axisPtr; + + axisPtr = Blt_GetHashValue(hPtr); + axisPtr->min = axisPtr->valueRange.min = DBL_MAX; + axisPtr->max = axisPtr->valueRange.max = -DBL_MAX; + } + + /* + * Step 2: For each element that's to be displayed, get the smallest + * and largest data values mapped to each X and Y-axis. This + * will be the axis limits if the user doesn't override them + * with -min and -max options. + */ + for (link = Blt_Chain_FirstLink(graphPtr->elements.displayList); + link != NULL; link = Blt_Chain_NextLink(link)) { + Element *elemPtr; + Region2d exts; + + elemPtr = Blt_Chain_GetValue(link); + if ((graphPtr->flags & UNMAP_HIDDEN) && (elemPtr->flags & HIDE)) { + continue; + } + (*elemPtr->procsPtr->extentsProc) (elemPtr, &exts); + GetDataLimits(elemPtr->axes.x, exts.left, exts.right); + GetDataLimits(elemPtr->axes.y, exts.top, exts.bottom); + } + /* + * Step 3: Now that we know the range of data values for each axis, + * set axis limits and compute a sweep to generate tick values. + */ + for (hPtr = Blt_FirstHashEntry(&graphPtr->axes.table, &cursor); + hPtr != NULL; hPtr = Blt_NextHashEntry(&cursor)) { + Axis *axisPtr; + double min, max; + + axisPtr = Blt_GetHashValue(hPtr); + FixAxisRange(axisPtr); + + /* Calculate min/max tick (major/minor) layouts */ + min = axisPtr->min; + max = axisPtr->max; + if ((DEFINED(axisPtr->scrollMin)) && (min < axisPtr->scrollMin)) { + min = axisPtr->scrollMin; + } + if ((DEFINED(axisPtr->scrollMax)) && (max > axisPtr->scrollMax)) { + max = axisPtr->scrollMax; + } + if (axisPtr->logScale) { + LogScaleAxis(axisPtr, min, max); + } else if (axisPtr->timeScale) { + TimeScaleAxis(axisPtr, min, max); + } else { + LinearScaleAxis(axisPtr, min, max); + } + + if ((axisPtr->flags & (DIRTY|AXIS_USE)) == (DIRTY|AXIS_USE)) { + graphPtr->flags |= CACHE_DIRTY; + } + } + + graphPtr->flags &= ~RESET_AXES; + + /* + * When any axis changes, we need to layout the entire graph. + */ + graphPtr->flags |= (GET_AXIS_GEOMETRY | LAYOUT_NEEDED | MAP_ALL | + REDRAW_WORLD); +} + +/* + *--------------------------------------------------------------------------- + * + * ResetTextStyles -- + * + * Configures axis attributes (font, line width, label, etc) and + * allocates a new (possibly shared) graphics context. Line cap style is + * projecting. This is for the problem of when a tick sits directly at + * the end point of the axis. + * + * Results: + * The return value is a standard TCL result. + * + * Side Effects: + * Axis resources are allocated (GC, font). Axis layout is deferred until + * the height and width of the window are known. + * + *--------------------------------------------------------------------------- + */ +static void +ResetTextStyles(Axis *axisPtr) +{ + Graph *graphPtr = axisPtr->obj.graphPtr; + GC newGC; + XGCValues gcValues; + unsigned long gcMask; + + Blt_Ts_ResetStyle(graphPtr->tkwin, &axisPtr->limitsTextStyle); + + gcMask = (GCForeground | GCLineWidth | GCCapStyle); + gcValues.foreground = axisPtr->tickColor->pixel; + gcValues.font = Blt_FontId(axisPtr->tickFont); + gcValues.line_width = LineWidth(axisPtr->lineWidth); + gcValues.cap_style = CapProjecting; + + newGC = Tk_GetGC(graphPtr->tkwin, gcMask, &gcValues); + if (axisPtr->tickGC != NULL) { + Tk_FreeGC(graphPtr->display, axisPtr->tickGC); + } + axisPtr->tickGC = newGC; + + /* Assuming settings from above GC */ + gcValues.foreground = axisPtr->activeFgColor->pixel; + newGC = Tk_GetGC(graphPtr->tkwin, gcMask, &gcValues); + if (axisPtr->activeTickGC != NULL) { + Tk_FreeGC(graphPtr->display, axisPtr->activeTickGC); + } + axisPtr->activeTickGC = newGC; + + gcValues.background = gcValues.foreground = axisPtr->major.color->pixel; + gcValues.line_width = LineWidth(axisPtr->major.lineWidth); + gcMask = (GCForeground | GCBackground | GCLineWidth); + if (LineIsDashed(axisPtr->major.dashes)) { + gcValues.line_style = LineOnOffDash; + gcMask |= GCLineStyle; + } + newGC = Blt_GetPrivateGC(graphPtr->tkwin, gcMask, &gcValues); + if (LineIsDashed(axisPtr->major.dashes)) { + Blt_SetDashes(graphPtr->display, newGC, &axisPtr->major.dashes); + } + if (axisPtr->major.gc != NULL) { + Blt_FreePrivateGC(graphPtr->display, axisPtr->major.gc); + } + axisPtr->major.gc = newGC; + + gcValues.background = gcValues.foreground = axisPtr->minor.color->pixel; + gcValues.line_width = LineWidth(axisPtr->minor.lineWidth); + gcMask = (GCForeground | GCBackground | GCLineWidth); + if (LineIsDashed(axisPtr->minor.dashes)) { + gcValues.line_style = LineOnOffDash; + gcMask |= GCLineStyle; + } + newGC = Blt_GetPrivateGC(graphPtr->tkwin, gcMask, &gcValues); + if (LineIsDashed(axisPtr->minor.dashes)) { + Blt_SetDashes(graphPtr->display, newGC, &axisPtr->minor.dashes); + } + if (axisPtr->minor.gc != NULL) { + Blt_FreePrivateGC(graphPtr->display, axisPtr->minor.gc); + } + axisPtr->minor.gc = newGC; +} + +/* + *--------------------------------------------------------------------------- + * + * DestroyAxis -- + * + * Results: + * None. + * + * Side effects: + * Resources (font, color, gc, labels, etc.) associated with the axis are + * deallocated. + * + *--------------------------------------------------------------------------- + */ +static void +DestroyAxis(Axis *axisPtr) +{ + Graph *graphPtr = axisPtr->obj.graphPtr; + int flags; + + flags = Blt_GraphType(graphPtr); + Blt_FreeOptions(configSpecs, (char *)axisPtr, graphPtr->display, flags); + if (graphPtr->bindTable != NULL) { + Blt_DeleteBindings(graphPtr->bindTable, axisPtr); + } + if (axisPtr->link != NULL) { + Blt_Chain_DeleteLink(axisPtr->chain, axisPtr->link); + } + if (axisPtr->obj.name != NULL) { + Blt_Free(axisPtr->obj.name); + } + if (axisPtr->hashPtr != NULL) { + Blt_DeleteHashEntry(&graphPtr->axes.table, axisPtr->hashPtr); + } + Blt_Ts_FreeStyle(graphPtr->display, &axisPtr->limitsTextStyle); + + if (axisPtr->tickGC != NULL) { + Tk_FreeGC(graphPtr->display, axisPtr->tickGC); + } + if (axisPtr->activeTickGC != NULL) { + Tk_FreeGC(graphPtr->display, axisPtr->activeTickGC); + } + if (axisPtr->major.gc != NULL) { + Blt_FreePrivateGC(graphPtr->display, axisPtr->major.gc); + } + if (axisPtr->minor.gc != NULL) { + Blt_FreePrivateGC(graphPtr->display, axisPtr->minor.gc); + } + FreeTickLabels(axisPtr->tickLabels); + Blt_Chain_Destroy(axisPtr->tickLabels); + if (axisPtr->segments != NULL) { + Blt_Free(axisPtr->segments); + } + Blt_Free(axisPtr); +} + +static void +FreeAxis(DestroyData data) +{ + Axis *axisPtr = (Axis *)data; + DestroyAxis(axisPtr); +} + +static float titleAngle[4] = /* Rotation for each axis title */ +{ + 0.0, 90.0, 0.0, 270.0 +}; + +/* + *--------------------------------------------------------------------------- + * + * AxisOffsets -- + * + * Determines the sites of the axis, major and minor ticks, and title of + * the axis. + * + * Results: + * None. + * + *--------------------------------------------------------------------------- + */ +static void +AxisOffsets( + Axis *axisPtr, + int margin, + int offset, + AxisInfo *infoPtr) +{ + Graph *graphPtr = axisPtr->obj.graphPtr; + Margin *marginPtr; + int pad; /* Offset of axis from interior + * region. This includes a possible + * border and the axis line width. */ + int axisLine; + int t1, t2, labelOffset; + int tickLabel, axisPad; + int inset, mark; + int x, y; + float fangle; + + axisPtr->titleAngle = titleAngle[margin]; + marginPtr = graphPtr->margins + margin; + + tickLabel = axisLine = t1 = t2 = 0; + labelOffset = AXIS_PAD_TITLE; + if (axisPtr->lineWidth > 0) { + if (axisPtr->flags & AXIS_SHOWTICKS) { + t1 = axisPtr->tickLength; + t2 = (t1 * 10) / 15; + } + labelOffset = t1 + AXIS_PAD_TITLE; + if (axisPtr->flags & AXIS_EXTERIOR) { + labelOffset += axisPtr->lineWidth; + } + } + axisPad = 0; + if (graphPtr->plotRelief != TK_RELIEF_SOLID) { + axisPad = 0; + } + /* Adjust offset for the interior border width and the line width */ + pad = 1; + if (graphPtr->plotBW > 0) { + pad += graphPtr->plotBW + 1; + } + pad = 0; /* FIXME: test */ + /* + * Pre-calculate the x-coordinate positions of the axis, tick labels, and + * the individual major and minor ticks. + */ + inset = pad + axisPtr->lineWidth / 2; + switch (margin) { + case MARGIN_TOP: + axisLine = graphPtr->top; + if (axisPtr->flags & AXIS_EXTERIOR) { + axisLine -= graphPtr->plotBW + axisPad + axisPtr->lineWidth / 2; + tickLabel = axisLine - 2; + if (axisPtr->lineWidth > 0) { + tickLabel -= axisPtr->tickLength; + } +#ifdef notdef + fprintf(stderr, "axisLine=%d, axisPad=%d plotBW=%d axisPtr->lineWidth/2=%d\n", axisLine, axisPad, graphPtr->plotBW, axisPtr->lineWidth /2 ); +#endif + } else { + if (graphPtr->plotRelief == TK_RELIEF_SOLID) { + axisLine--; + } + axisLine -= axisPad + axisPtr->lineWidth / 2; + tickLabel = graphPtr->top - graphPtr->plotBW - 2; +#ifdef notdef + fprintf(stderr, "axisLine=%d, axisPad=%d plotBW=%d axisPtr->lineWidth/2=%d\n", axisLine, axisPad, graphPtr->plotBW, axisPtr->lineWidth /2 ); +#endif + } + mark = graphPtr->top - offset - pad; + axisPtr->tickAnchor = TK_ANCHOR_S; + axisPtr->left = axisPtr->screenMin - inset - 2; + axisPtr->right = axisPtr->screenMin + axisPtr->screenRange + inset - 1; + if (graphPtr->stackAxes) { + axisPtr->top = mark - marginPtr->axesOffset; + } else { + axisPtr->top = mark - axisPtr->height; + } + axisPtr->bottom = mark; + if (axisPtr->titleAlternate) { + x = graphPtr->right + AXIS_PAD_TITLE; + y = mark - (axisPtr->height / 2); + axisPtr->titleAnchor = TK_ANCHOR_W; + } else { + x = (axisPtr->right + axisPtr->left) / 2; + if (graphPtr->stackAxes) { + y = mark - marginPtr->axesOffset + AXIS_PAD_TITLE; + } else { + y = mark - axisPtr->height + AXIS_PAD_TITLE; + } + axisPtr->titleAnchor = TK_ANCHOR_N; + } + axisPtr->titlePos.x = x; + axisPtr->titlePos.y = y; + break; + + case MARGIN_BOTTOM: + /* + * ----------- bottom + plot borderwidth + * mark -------------------------------------------- + * ===================== axisLine (linewidth) + * tick + * title + * + * ===================== axisLine (linewidth) + * ----------- bottom + plot borderwidth + * mark -------------------------------------------- + * tick + * title + */ + axisLine = graphPtr->bottom; + if (graphPtr->plotRelief == TK_RELIEF_SOLID) { + axisLine++; + } + if (axisPtr->flags & AXIS_EXTERIOR) { + axisLine += graphPtr->plotBW + axisPad + axisPtr->lineWidth / 2; + tickLabel = axisLine + 2; + if (axisPtr->lineWidth > 0) { + tickLabel += axisPtr->tickLength; + } + } else { + axisLine -= axisPad + axisPtr->lineWidth / 2; + tickLabel = graphPtr->bottom + graphPtr->plotBW + 2; + } + mark = graphPtr->bottom + offset; + fangle = FMOD(axisPtr->tickAngle, 90.0); + if (fangle == 0.0) { + axisPtr->tickAnchor = TK_ANCHOR_N; + } else { + int quadrant; + + quadrant = (int)(axisPtr->tickAngle / 90.0); + if ((quadrant == 0) || (quadrant == 2)) { + axisPtr->tickAnchor = TK_ANCHOR_NE; + } else { + axisPtr->tickAnchor = TK_ANCHOR_NW; + } + } + axisPtr->left = axisPtr->screenMin - inset - 2; + axisPtr->right = axisPtr->screenMin + axisPtr->screenRange + inset - 1; + axisPtr->top = graphPtr->bottom + labelOffset - t1; + if (graphPtr->stackAxes) { + axisPtr->bottom = mark + marginPtr->axesOffset - 1; + } else { + axisPtr->bottom = mark + axisPtr->height - 1; + } + if (axisPtr->titleAlternate) { + x = graphPtr->right + AXIS_PAD_TITLE; + y = mark + (axisPtr->height / 2); + axisPtr->titleAnchor = TK_ANCHOR_W; + } else { + x = (axisPtr->right + axisPtr->left) / 2; + if (graphPtr->stackAxes) { + y = mark + marginPtr->axesOffset - AXIS_PAD_TITLE; + } else { + y = mark + axisPtr->height - AXIS_PAD_TITLE; + } + axisPtr->titleAnchor = TK_ANCHOR_S; + } + axisPtr->titlePos.x = x; + axisPtr->titlePos.y = y; + break; + + case MARGIN_LEFT: + /* + * mark + * | : + * | : + * | : + * | : + * | : + * axisLine + */ + /* + * Exterior axis + * + plotarea right + * |A|B|C|D|E|F|G|H + * |right + * A = plot pad + * B = plot border width + * C = axis pad + * D = axis line + * E = tick length + * F = tick label + * G = graph border width + * H = highlight thickness + */ + /* + * Interior axis + * + plotarea right + * |A|B|C|D|E|F|G|H + * |right + * A = plot pad + * B = tick length + * C = axis line width + * D = axis pad + * E = plot border width + * F = tick label + * G = graph border width + * H = highlight thickness + */ + axisLine = graphPtr->left; + if (axisPtr->flags & AXIS_EXTERIOR) { + axisLine -= graphPtr->plotBW + axisPad + axisPtr->lineWidth / 2; + tickLabel = axisLine - 2; + if (axisPtr->lineWidth > 0) { + tickLabel -= axisPtr->tickLength; + } + } else { + if (graphPtr->plotRelief == TK_RELIEF_SOLID) { + axisLine--; + } + axisLine += axisPad + axisPtr->lineWidth / 2; + tickLabel = graphPtr->left - graphPtr->plotBW - 2; + } + mark = graphPtr->left - offset; + axisPtr->tickAnchor = TK_ANCHOR_E; + if (graphPtr->stackAxes) { + axisPtr->left = mark - marginPtr->axesOffset; + } else { + axisPtr->left = mark - axisPtr->width; + } + axisPtr->right = mark - 3; + axisPtr->top = axisPtr->screenMin - inset - 2; + axisPtr->bottom = axisPtr->screenMin + axisPtr->screenRange + inset - 1; + if (axisPtr->titleAlternate) { + x = mark - (axisPtr->width / 2); + y = graphPtr->top - AXIS_PAD_TITLE; + axisPtr->titleAnchor = TK_ANCHOR_SW; + } else { + if (graphPtr->stackAxes) { + x = mark - marginPtr->axesOffset; + } else { + x = mark - axisPtr->width + AXIS_PAD_TITLE; + } + y = (axisPtr->bottom + axisPtr->top) / 2; + axisPtr->titleAnchor = TK_ANCHOR_W; + } + axisPtr->titlePos.x = x; + axisPtr->titlePos.y = y; + break; + + case MARGIN_RIGHT: + axisLine = graphPtr->right; + if (graphPtr->plotRelief == TK_RELIEF_SOLID) { + axisLine++; /* Draw axis line within solid plot + * border. */ + } + if (axisPtr->flags & AXIS_EXTERIOR) { + axisLine += graphPtr->plotBW + axisPad + axisPtr->lineWidth / 2; + tickLabel = axisLine + 2; + if (axisPtr->lineWidth > 0) { + tickLabel += axisPtr->tickLength; + } + } else { + axisLine -= axisPad + axisPtr->lineWidth / 2; + tickLabel = graphPtr->right + graphPtr->plotBW + 2; + } + mark = graphPtr->right + offset + pad; + axisPtr->tickAnchor = TK_ANCHOR_W; + axisPtr->left = mark; + if (graphPtr->stackAxes) { + axisPtr->right = mark + marginPtr->axesOffset - 1; + } else { + axisPtr->right = mark + axisPtr->width - 1; + } + axisPtr->top = axisPtr->screenMin - inset - 2; + axisPtr->bottom = axisPtr->screenMin + axisPtr->screenRange + inset -1; + if (axisPtr->titleAlternate) { + x = mark + (axisPtr->width / 2); + y = graphPtr->top - AXIS_PAD_TITLE; + axisPtr->titleAnchor = TK_ANCHOR_SE; + } else { + if (graphPtr->stackAxes) { + x = mark + marginPtr->axesOffset - AXIS_PAD_TITLE; + } else { + x = mark + axisPtr->width - AXIS_PAD_TITLE; + } + y = (axisPtr->bottom + axisPtr->top) / 2; + axisPtr->titleAnchor = TK_ANCHOR_E; + } + axisPtr->titlePos.x = x; + axisPtr->titlePos.y = y; + break; + + case MARGIN_NONE: + axisLine = 0; + break; + } + if ((margin == MARGIN_LEFT) || (margin == MARGIN_TOP)) { + t1 = -t1, t2 = -t2; + labelOffset = -labelOffset; + } + infoPtr->axis = axisLine; + infoPtr->t1 = axisLine + t1; + infoPtr->t2 = axisLine + t2; + if (tickLabel > 0) { + infoPtr->label = tickLabel; + } else { + infoPtr->label = axisLine + labelOffset; + } + if ((axisPtr->flags & AXIS_EXTERIOR) == 0) { + /*infoPtr->label = axisLine + labelOffset - t1; */ + infoPtr->t1 = axisLine - t1; + infoPtr->t2 = axisLine - t2; + } +} + +static void +MakeAxisLine(Axis *axisPtr, int line, Segment2d *sp) +{ + double min, max; + + min = axisPtr->axisRange.min; + max = axisPtr->axisRange.max; + if (axisPtr->logScale) { + min = EXP10(min); + max = EXP10(max); + } + if (AxisIsHorizontal(axisPtr)) { + sp->p.x = Blt_HMap(axisPtr, min); + sp->q.x = Blt_HMap(axisPtr, max); + sp->p.y = sp->q.y = line; + } else { + sp->q.x = sp->p.x = line; + sp->p.y = Blt_VMap(axisPtr, min); + sp->q.y = Blt_VMap(axisPtr, max); + } +} + + +static void +MakeTick(Axis *axisPtr, double value, int tick, int line, Segment2d *sp) +{ + if (axisPtr->logScale) { + value = EXP10(value); + } + if (AxisIsHorizontal(axisPtr)) { + sp->p.x = sp->q.x = Blt_HMap(axisPtr, value); + sp->p.y = line; + sp->q.y = tick; + } else { + sp->p.x = line; + sp->p.y = sp->q.y = Blt_VMap(axisPtr, value); + sp->q.x = tick; + } +} + +static void +MakeSegments(Axis *axisPtr, AxisInfo *infoPtr) +{ + int arraySize; + int nMajorTicks, nMinorTicks; + Segment2d *segments; + Segment2d *sp; + + if (axisPtr->segments != NULL) { + Blt_Free(axisPtr->segments); + } + nMajorTicks = nMinorTicks = 0; + if (axisPtr->t1Ptr != NULL) { + nMajorTicks = axisPtr->t1Ptr->nTicks; + } + if (axisPtr->t2Ptr != NULL) { + nMinorTicks = axisPtr->t2Ptr->nTicks; + } + arraySize = 1 + (nMajorTicks * (nMinorTicks + 1)); + segments = Blt_AssertMalloc(arraySize * sizeof(Segment2d)); + sp = segments; + if (axisPtr->lineWidth > 0) { + /* Axis baseline */ + MakeAxisLine(axisPtr, infoPtr->axis, sp); + sp++; + } + if (axisPtr->flags & AXIS_SHOWTICKS) { + Blt_ChainLink link; + double labelPos; + int i; + int isHoriz; + + isHoriz = AxisIsHorizontal(axisPtr); + for (i = 0; i < nMajorTicks; i++) { + double t1, t2; + int j; + + t1 = axisPtr->t1Ptr->values[i]; + /* Minor ticks */ + for (j = 0; j < nMinorTicks; j++) { + t2 = t1 + (axisPtr->majorSweep.step * + axisPtr->t2Ptr->values[j]); + if (InRange(t2, &axisPtr->axisRange)) { + MakeTick(axisPtr, t2, infoPtr->t2, infoPtr->axis, sp); + sp++; + } + } + if (!InRange(t1, &axisPtr->axisRange)) { + continue; + } + /* Major tick */ + MakeTick(axisPtr, t1, infoPtr->t1, infoPtr->axis, sp); + sp++; + } + + link = Blt_Chain_FirstLink(axisPtr->tickLabels); + labelPos = (double)infoPtr->label; + + for (i = 0; i < nMajorTicks; i++) { + double t1; + TickLabel *labelPtr; + Segment2d seg; + + t1 = axisPtr->t1Ptr->values[i]; + if (axisPtr->labelOffset) { + t1 += axisPtr->majorSweep.step * 0.5; + } + if (!InRange(t1, &axisPtr->axisRange)) { + continue; + } + labelPtr = Blt_Chain_GetValue(link); + link = Blt_Chain_NextLink(link); + MakeTick(axisPtr, t1, infoPtr->t1, infoPtr->axis, &seg); + /* Save tick label X-Y position. */ + if (isHoriz) { + labelPtr->anchorPos.x = seg.p.x; + labelPtr->anchorPos.y = labelPos; + } else { + labelPtr->anchorPos.x = labelPos; + labelPtr->anchorPos.y = seg.p.y; + } + } + } + axisPtr->segments = segments; + axisPtr->nSegments = sp - segments; + assert(axisPtr->nSegments <= arraySize); +} + +/* + *--------------------------------------------------------------------------- + * + * MapAxis -- + * + * Pre-calculates positions of the axis, ticks, and labels (to be used + * later when displaying the axis). Calculates the values for each major + * and minor tick and checks to see if they are in range (the outer ticks + * may be outside of the range of plotted values). + * + * Line segments for the minor and major ticks are saved into one + * XSegment array so that they can be drawn by a single XDrawSegments + * call. The positions of the tick labels are also computed and saved. + * + * Results: + * None. + * + * Side Effects: + * Line segments and tick labels are saved and used later to draw the + * axis. + * + *--------------------------------------------------------------------------- + */ +static void +MapAxis(Axis *axisPtr, int offset, int margin) +{ + AxisInfo info; + Graph *graphPtr = axisPtr->obj.graphPtr; + + if (AxisIsHorizontal(axisPtr)) { + axisPtr->screenMin = graphPtr->hOffset; + axisPtr->width = graphPtr->right - graphPtr->left; + axisPtr->screenRange = graphPtr->hRange; + } else { + axisPtr->screenMin = graphPtr->vOffset; + axisPtr->height = graphPtr->bottom - graphPtr->top; + axisPtr->screenRange = graphPtr->vRange; + } + axisPtr->screenScale = 1.0 / axisPtr->screenRange; + AxisOffsets(axisPtr, margin, offset, &info); + MakeSegments(axisPtr, &info); +} + +/* + *--------------------------------------------------------------------------- + * + * MapStackedAxis -- + * + * Pre-calculates positions of the axis, ticks, and labels (to be used + * later when displaying the axis). Calculates the values for each major + * and minor tick and checks to see if they are in range (the outer ticks + * may be outside of the range of plotted values). + * + * Line segments for the minor and major ticks are saved into one XSegment + * array so that they can be drawn by a single XDrawSegments call. The + * positions of the tick labels are also computed and saved. + * + * Results: + * None. + * + * Side Effects: + * Line segments and tick labels are saved and used later to draw the + * axis. + * + *--------------------------------------------------------------------------- + */ +static void +MapStackedAxis(Axis *axisPtr, int count, int margin) +{ + AxisInfo info; + Graph *graphPtr = axisPtr->obj.graphPtr; + unsigned int slice, w, h; + + if ((graphPtr->margins[axisPtr->margin].axes->nLinks > 1) || + (axisPtr->reqNumMajorTicks <= 0)) { + axisPtr->reqNumMajorTicks = 4; + } + if (AxisIsHorizontal(axisPtr)) { + slice = graphPtr->hRange / graphPtr->margins[margin].axes->nLinks; + axisPtr->screenMin = graphPtr->hOffset; + axisPtr->width = slice; + } else { + slice = graphPtr->vRange / graphPtr->margins[margin].axes->nLinks; + axisPtr->screenMin = graphPtr->vOffset; + axisPtr->height = slice; + } +#define AXIS_PAD 2 + Blt_GetTextExtents(axisPtr->tickFont, 0, "0", 1, &w, &h); + axisPtr->screenMin += (slice * count) + AXIS_PAD + h / 2; + axisPtr->screenRange = slice - 2 * AXIS_PAD - h; + axisPtr->screenScale = 1.0f / axisPtr->screenRange; + AxisOffsets(axisPtr, margin, 0, &info); + MakeSegments(axisPtr, &info); +} + +/* + *--------------------------------------------------------------------------- + * + * AdjustViewport -- + * + * Adjusts the offsets of the viewport according to the scroll mode. This + * is to accommodate both "listbox" and "canvas" style scrolling. + * + * "canvas" The viewport scrolls within the range of world + * coordinates. This way the viewport always displays + * a full page of the world. If the world is smaller + * than the viewport, then (bizarrely) the world and + * viewport are inverted so that the world moves up + * and down within the viewport. + * + * "listbox" The viewport can scroll beyond the range of world + * coordinates. Every entry can be displayed at the + * top of the viewport. This also means that the + * scrollbar thumb weirdly shrinks as the last entry + * is scrolled upward. + * + * Results: + * The corrected offset is returned. + * + *--------------------------------------------------------------------------- + */ +static double +AdjustViewport(double offset, double windowSize) +{ + /* + * Canvas-style scrolling allows the world to be scrolled within the window. + */ + if (windowSize > 1.0) { + if (windowSize < (1.0 - offset)) { + offset = 1.0 - windowSize; + } + if (offset > 0.0) { + offset = 0.0; + } + } else { + if ((offset + windowSize) > 1.0) { + offset = 1.0 - windowSize; + } + if (offset < 0.0) { + offset = 0.0; + } + } + return offset; +} + +static int +GetAxisScrollInfo( + Tcl_Interp *interp, + int objc, + Tcl_Obj *const *objv, + double *offsetPtr, + double windowSize, + double scrollUnits, + double scale) +{ + const char *string; + char c; + double offset; + int length; + + offset = *offsetPtr; + string = Tcl_GetStringFromObj(objv[0], &length); + c = string[0]; + scrollUnits *= scale; + if ((c == 's') && (strncmp(string, "scroll", length) == 0)) { + int count; + double fract; + + assert(objc == 3); + /* Scroll number unit/page */ + if (Tcl_GetIntFromObj(interp, objv[1], &count) != TCL_OK) { + return TCL_ERROR; + } + string = Tcl_GetStringFromObj(objv[2], &length); + c = string[0]; + if ((c == 'u') && (strncmp(string, "units", length) == 0)) { + fract = count * scrollUnits; + } else if ((c == 'p') && (strncmp(string, "pages", length) == 0)) { + /* A page is 90% of the view-able window. */ + fract = (int)(count * windowSize * 0.9 + 0.5); + } else if ((c == 'p') && (strncmp(string, "pixels", length) == 0)) { + fract = count * scale; + } else { + Tcl_AppendResult(interp, "unknown \"scroll\" units \"", string, + "\"", (char *)NULL); + return TCL_ERROR; + } + offset += fract; + } else if ((c == 'm') && (strncmp(string, "moveto", length) == 0)) { + double fract; + + assert(objc == 2); + /* moveto fraction */ + if (Blt_GetDoubleFromObj(interp, objv[1], &fract) != TCL_OK) { + return TCL_ERROR; + } + offset = fract; + } else { + int count; + double fract; + + /* Treat like "scroll units" */ + if (Tcl_GetIntFromObj(interp, objv[0], &count) != TCL_OK) { + return TCL_ERROR; + } + fract = (double)count * scrollUnits; + offset += fract; + /* CHECK THIS: return TCL_OK; */ + } + *offsetPtr = AdjustViewport(offset, windowSize); + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * DrawAxis -- + * + * Draws the axis, ticks, and labels onto the canvas. + * + * Initializes and passes text attribute information through TextStyle + * structure. + * + * Results: + * None. + * + * Side Effects: + * Axis gets drawn on window. + * + *--------------------------------------------------------------------------- + */ +static void +DrawAxis(Axis *axisPtr, Drawable drawable) +{ + Graph *graphPtr = axisPtr->obj.graphPtr; + + if (axisPtr->normalBg != NULL) { + Blt_FillBackgroundRectangle(graphPtr->tkwin, drawable, + axisPtr->normalBg, + axisPtr->left, axisPtr->top, + axisPtr->right - axisPtr->left, + axisPtr->bottom - axisPtr->top, axisPtr->borderWidth, + axisPtr->relief); + } + if (axisPtr->title != NULL) { + TextStyle ts; + + Blt_Ts_InitStyle(ts); + Blt_Ts_SetAngle(ts, axisPtr->titleAngle); + Blt_Ts_SetFont(ts, axisPtr->titleFont); + Blt_Ts_SetPadding(ts, 1, 2, 0, 0); + Blt_Ts_SetAnchor(ts, axisPtr->titleAnchor); + Blt_Ts_SetJustify(ts, axisPtr->titleJustify); + if (axisPtr->flags & ACTIVE) { + Blt_Ts_SetForeground(ts, axisPtr->activeFgColor); + } else { + Blt_Ts_SetForeground(ts, axisPtr->titleColor); + } + Blt_Ts_SetForeground(ts, axisPtr->titleColor); + if ((axisPtr->titleAngle == 90.0) || (axisPtr->titleAngle == 270.0)) { + Blt_Ts_SetMaxLength(ts, axisPtr->height); + } else { + Blt_Ts_SetMaxLength(ts, axisPtr->width); + } + Blt_Ts_DrawText(graphPtr->tkwin, drawable, axisPtr->title, -1, &ts, + (int)axisPtr->titlePos.x, (int)axisPtr->titlePos.y); + } + if (axisPtr->scrollCmdObjPtr != NULL) { + double viewWidth, viewMin, viewMax; + double worldWidth, worldMin, worldMax; + double fract; + int isHoriz; + + worldMin = axisPtr->valueRange.min; + worldMax = axisPtr->valueRange.max; + if (DEFINED(axisPtr->scrollMin)) { + worldMin = axisPtr->scrollMin; + } + if (DEFINED(axisPtr->scrollMax)) { + worldMax = axisPtr->scrollMax; + } + viewMin = axisPtr->min; + viewMax = axisPtr->max; + if (viewMin < worldMin) { + viewMin = worldMin; + } + if (viewMax > worldMax) { + viewMax = worldMax; + } + if (axisPtr->logScale) { + worldMin = log10(worldMin); + worldMax = log10(worldMax); + viewMin = log10(viewMin); + viewMax = log10(viewMax); + } + worldWidth = worldMax - worldMin; + viewWidth = viewMax - viewMin; + isHoriz = AxisIsHorizontal(axisPtr); + + if (isHoriz != axisPtr->descending) { + fract = (viewMin - worldMin) / worldWidth; + } else { + fract = (worldMax - viewMax) / worldWidth; + } + fract = AdjustViewport(fract, viewWidth / worldWidth); + + if (isHoriz != axisPtr->descending) { + viewMin = (fract * worldWidth); + axisPtr->min = viewMin + worldMin; + axisPtr->max = axisPtr->min + viewWidth; + viewMax = viewMin + viewWidth; + if (axisPtr->logScale) { + axisPtr->min = EXP10(axisPtr->min); + axisPtr->max = EXP10(axisPtr->max); + } + Blt_UpdateScrollbar(graphPtr->interp, axisPtr->scrollCmdObjPtr, + viewMin, viewMax, worldWidth); + } else { + viewMax = (fract * worldWidth); + axisPtr->max = worldMax - viewMax; + axisPtr->min = axisPtr->max - viewWidth; + viewMin = viewMax + viewWidth; + if (axisPtr->logScale) { + axisPtr->min = EXP10(axisPtr->min); + axisPtr->max = EXP10(axisPtr->max); + } + Blt_UpdateScrollbar(graphPtr->interp, axisPtr->scrollCmdObjPtr, + viewMax, viewMin, worldWidth); + } + } + if (axisPtr->flags & AXIS_SHOWTICKS) { + Blt_ChainLink link; + TextStyle ts; + + Blt_Ts_InitStyle(ts); + Blt_Ts_SetAngle(ts, axisPtr->tickAngle); + Blt_Ts_SetFont(ts, axisPtr->tickFont); + Blt_Ts_SetPadding(ts, 2, 0, 0, 0); + Blt_Ts_SetAnchor(ts, axisPtr->tickAnchor); + if (axisPtr->flags & ACTIVE) { + Blt_Ts_SetForeground(ts, axisPtr->activeFgColor); + } else { + Blt_Ts_SetForeground(ts, axisPtr->tickColor); + } + for (link = Blt_Chain_FirstLink(axisPtr->tickLabels); link != NULL; + link = Blt_Chain_NextLink(link)) { + TickLabel *labelPtr; + + labelPtr = Blt_Chain_GetValue(link); + /* Draw major tick labels */ + Blt_DrawText(graphPtr->tkwin, drawable, labelPtr->string, &ts, + (int)labelPtr->anchorPos.x, (int)labelPtr->anchorPos.y); + } + } + if ((axisPtr->nSegments > 0) && (axisPtr->lineWidth > 0)) { + GC gc; + + if (axisPtr->flags & ACTIVE) { + gc = axisPtr->activeTickGC; + } else { + gc = axisPtr->tickGC; + } + /* Draw the tick marks and axis line. */ + Blt_Draw2DSegments(graphPtr->display, drawable, gc, axisPtr->segments, + axisPtr->nSegments); + } +} + +/* + *--------------------------------------------------------------------------- + * + * AxisToPostScript -- + * + * Generates PostScript output to draw the axis, ticks, and labels. + * + * Initializes and passes text attribute information through TextStyle + * structure. + * + * Results: + * None. + * + * Side Effects: + * PostScript output is left in graphPtr->interp->result; + * + *--------------------------------------------------------------------------- + */ +/* ARGSUSED */ +static void +AxisToPostScript(Blt_Ps ps, Axis *axisPtr) +{ + Blt_Ps_Format(ps, "%% Axis \"%s\"\n", axisPtr->obj.name); + if (axisPtr->normalBg != NULL) { + Tk_3DBorder border; + + border = Blt_BackgroundBorder(axisPtr->normalBg); + Blt_Ps_Fill3DRectangle(ps, border, + (double)axisPtr->left, (double)axisPtr->top, + axisPtr->right - axisPtr->left, axisPtr->bottom - axisPtr->top, + axisPtr->borderWidth, axisPtr->relief); + } + if (axisPtr->title != NULL) { + TextStyle ts; + + Blt_Ts_InitStyle(ts); + Blt_Ts_SetAngle(ts, axisPtr->titleAngle); + Blt_Ts_SetFont(ts, axisPtr->titleFont); + Blt_Ts_SetPadding(ts, 1, 2, 0, 0); + Blt_Ts_SetAnchor(ts, axisPtr->titleAnchor); + Blt_Ts_SetJustify(ts, axisPtr->titleJustify); + Blt_Ts_SetForeground(ts, axisPtr->titleColor); + Blt_Ps_DrawText(ps, axisPtr->title, &ts, axisPtr->titlePos.x, + axisPtr->titlePos.y); + } + if (axisPtr->flags & AXIS_SHOWTICKS) { + Blt_ChainLink link; + TextStyle ts; + + Blt_Ts_InitStyle(ts); + Blt_Ts_SetAngle(ts, axisPtr->tickAngle); + Blt_Ts_SetFont(ts, axisPtr->tickFont); + Blt_Ts_SetPadding(ts, 2, 0, 0, 0); + Blt_Ts_SetAnchor(ts, axisPtr->tickAnchor); + Blt_Ts_SetForeground(ts, axisPtr->tickColor); + + for (link = Blt_Chain_FirstLink(axisPtr->tickLabels); link != NULL; + link = Blt_Chain_NextLink(link)) { + TickLabel *labelPtr; + + labelPtr = Blt_Chain_GetValue(link); + Blt_Ps_DrawText(ps, labelPtr->string, &ts, labelPtr->anchorPos.x, + labelPtr->anchorPos.y); + } + } + if ((axisPtr->nSegments > 0) && (axisPtr->lineWidth > 0)) { + Blt_Ps_XSetLineAttributes(ps, axisPtr->tickColor, axisPtr->lineWidth, + (Blt_Dashes *)NULL, CapButt, JoinMiter); + Blt_Ps_Draw2DSegments(ps, axisPtr->segments, axisPtr->nSegments); + } +} + +static void +MakeGridLine(Axis *axisPtr, double value, Segment2d *sp) +{ + Graph *graphPtr = axisPtr->obj.graphPtr; + + if (axisPtr->logScale) { + value = EXP10(value); + } + /* Grid lines run orthogonally to the axis */ + if (AxisIsHorizontal(axisPtr)) { + sp->p.y = graphPtr->top; + sp->q.y = graphPtr->bottom; + sp->p.x = sp->q.x = Blt_HMap(axisPtr, value); + } else { + sp->p.x = graphPtr->left; + sp->q.x = graphPtr->right; + sp->p.y = sp->q.y = Blt_VMap(axisPtr, value); + } +} + +/* + *--------------------------------------------------------------------------- + * + * MapGridlines -- + * + * Assembles the grid lines associated with an axis. Generates tick + * positions if necessary (this happens when the axis is not a logical axis + * too). + * + * Results: + * None. + * + *--------------------------------------------------------------------------- + */ +static void +MapGridlines(Axis *axisPtr) +{ + Segment2d *s1, *s2; + Ticks *t1Ptr, *t2Ptr; + int needed; + int i; + + if (axisPtr == NULL) { + return; + } + t1Ptr = axisPtr->t1Ptr; + if (t1Ptr == NULL) { + t1Ptr = GenerateTicks(&axisPtr->majorSweep); + } + t2Ptr = axisPtr->t2Ptr; + if (t2Ptr == NULL) { + t2Ptr = GenerateTicks(&axisPtr->minorSweep); + } + needed = t1Ptr->nTicks; + if (axisPtr->flags & AXIS_GRIDMINOR) { + needed += (t1Ptr->nTicks * t2Ptr->nTicks); + } + if (needed == 0) { + return; + } + needed = t1Ptr->nTicks; + if (needed != axisPtr->major.nAllocated) { + if (axisPtr->major.segments != NULL) { + Blt_Free(axisPtr->major.segments); + } + axisPtr->major.segments = Blt_AssertMalloc(sizeof(Segment2d) * needed); + axisPtr->major.nAllocated = needed; + } + needed = (t1Ptr->nTicks * t2Ptr->nTicks); + if (needed != axisPtr->minor.nAllocated) { + if (axisPtr->minor.segments != NULL) { + Blt_Free(axisPtr->minor.segments); + } + axisPtr->minor.segments = Blt_AssertMalloc(sizeof(Segment2d) * needed); + axisPtr->minor.nAllocated = needed; + } + s1 = axisPtr->major.segments, s2 = axisPtr->minor.segments; + for (i = 0; i < t1Ptr->nTicks; i++) { + double value; + + value = t1Ptr->values[i]; + if (axisPtr->flags & AXIS_GRIDMINOR) { + int j; + + for (j = 0; j < t2Ptr->nTicks; j++) { + double subValue; + + subValue = value + (axisPtr->majorSweep.step * + t2Ptr->values[j]); + if (InRange(subValue, &axisPtr->axisRange)) { + MakeGridLine(axisPtr, subValue, s2); + s2++; + } + } + } + if (InRange(value, &axisPtr->axisRange)) { + MakeGridLine(axisPtr, value, s1); + s1++; + } + } + if (t1Ptr != axisPtr->t1Ptr) { + Blt_Free(t1Ptr); /* Free generated ticks. */ + } + if (t2Ptr != axisPtr->t2Ptr) { + Blt_Free(t2Ptr); /* Free generated ticks. */ + } + axisPtr->major.nUsed = s1 - axisPtr->major.segments; + axisPtr->minor.nUsed = s2 - axisPtr->minor.segments; +} + +/* + *--------------------------------------------------------------------------- + * + * GetAxisGeometry -- + * + * Results: + * None. + * + * Exterior axis: + * l r + * |a|b|c|d|e|f|g|h|i| j |i|h|g|f|e|d|c|d|a| + * + * Interior axis: + * l r + * |a|b|c|d|h|g|f|e| j |e|f|g|h|d|c|b|a| + * i.. ..i + * a = highlight thickness + * b = graph borderwidth + * c = axis title + * d = tick label + * e = tick + * f = axis line + * g = 1 pixel pad + * h = plot borderwidth + * i = plot pad + * j = plot area + *--------------------------------------------------------------------------- + */ +static void +GetAxisGeometry(Graph *graphPtr, Axis *axisPtr) +{ + unsigned int y; + + FreeTickLabels(axisPtr->tickLabels); + y = 0; + + if ((axisPtr->flags & AXIS_EXTERIOR) && + (graphPtr->plotRelief != TK_RELIEF_SOLID)) { + /* Leave room for axis baseline and padding */ + y += axisPtr->lineWidth + 2; + } + + axisPtr->maxTickHeight = axisPtr->maxTickWidth = 0; + if (axisPtr->flags & AXIS_SHOWTICKS) { + unsigned int pad; + unsigned int i, nLabels, nTicks; + + SweepTicks(axisPtr); + + nTicks = 0; + if (axisPtr->t1Ptr != NULL) { + nTicks = axisPtr->t1Ptr->nTicks; + } + assert(nTicks <= MAXTICKS); + + nLabels = 0; + for (i = 0; i < nTicks; i++) { + TickLabel *labelPtr; + double x, x2; + unsigned int lw, lh; /* Label width and height. */ + + x2 = x = axisPtr->t1Ptr->values[i]; + if (axisPtr->labelOffset) { + x2 += axisPtr->majorSweep.step * 0.5; + } + if (!InRange(x2, &axisPtr->axisRange)) { + continue; + } + labelPtr = MakeLabel(axisPtr, x); + Blt_Chain_Append(axisPtr->tickLabels, labelPtr); + nLabels++; + /* + * Get the dimensions of each tick label. Remember tick labels + * can be multi-lined and/or rotated. + */ + Blt_GetTextExtents(axisPtr->tickFont, 0, labelPtr->string, -1, + &lw, &lh); + labelPtr->width = lw; + labelPtr->height = lh; + + if (axisPtr->tickAngle != 0.0f) { + double rlw, rlh; /* Rotated label width and height. */ + Blt_GetBoundingBox(lw, lh, axisPtr->tickAngle, &rlw, &rlh,NULL); + lw = ROUND(rlw), lh = ROUND(rlh); + } + if (axisPtr->maxTickWidth < lw) { + axisPtr->maxTickWidth = lw; + } + if (axisPtr->maxTickHeight < lh) { + axisPtr->maxTickHeight = lh; + } + } + assert(nLabels <= nTicks); + + pad = 0; + if (axisPtr->flags & AXIS_EXTERIOR) { + /* Because the axis cap style is "CapProjecting", we need to + * account for an extra 1.5 linewidth at the end of each line. */ + pad = ((axisPtr->lineWidth * 12) / 8); + } + if (AxisIsHorizontal(axisPtr)) { + y += axisPtr->maxTickHeight + pad; + } else { + y += axisPtr->maxTickWidth + pad; + if (axisPtr->maxTickWidth > 0) { + y += 5; /* Pad either size of label. */ + } + } + y += 2 * AXIS_PAD_TITLE; + if ((axisPtr->lineWidth > 0) && (axisPtr->flags & AXIS_EXTERIOR)) { + /* Distance from axis line to tick label. */ + y += axisPtr->tickLength; + } + } + + if (axisPtr->title != NULL) { + if (axisPtr->titleAlternate) { + if (y < axisPtr->titleHeight) { + y = axisPtr->titleHeight; + } + } else { + y += axisPtr->titleHeight + AXIS_PAD_TITLE; + } + } + + /* Correct for orientation of the axis. */ + if (AxisIsHorizontal(axisPtr)) { + axisPtr->height = y; + } else { + axisPtr->width = y; + } +} + +/* + *--------------------------------------------------------------------------- + * + * GetMarginGeometry -- + * + * Examines all the axes in the given margin and determines the area + * required to display them. + * + * Note: For multiple axes, the titles are displayed in another + * margin. So we must keep track of the widest title. + * + * Results: + * Returns the width or height of the margin, depending if it runs + * horizontally along the graph or vertically. + * + * Side Effects: + * The area width and height set in the margin. Note again that this may + * be corrected later (mulitple axes) to adjust for the longest title in + * another margin. + * + *--------------------------------------------------------------------------- + */ +static int +GetMarginGeometry(Graph *graphPtr, Margin *marginPtr) +{ + Blt_ChainLink link; + unsigned int l, w, h; /* Length, width, and height. */ + int isHoriz; + unsigned int nVisible; + + isHoriz = HORIZMARGIN(marginPtr); + + /* Count the visible axes. */ + nVisible = 0; + l = w = h = 0; + marginPtr->maxTickWidth = marginPtr->maxTickHeight = 0; + if (graphPtr->stackAxes) { + for (link = Blt_Chain_FirstLink(marginPtr->axes); link != NULL; + link = Blt_Chain_NextLink(link)) { + Axis *axisPtr; + + axisPtr = Blt_Chain_GetValue(link); + if ((axisPtr->flags & (HIDE|AXIS_USE)) == AXIS_USE) { + nVisible++; + if (graphPtr->flags & GET_AXIS_GEOMETRY) { + GetAxisGeometry(graphPtr, axisPtr); + } + if (isHoriz) { + if (h < axisPtr->height) { + h = axisPtr->height; + } + } else { + if (w < axisPtr->width) { + w = axisPtr->width; + } + } + if (axisPtr->maxTickWidth > marginPtr->maxTickWidth) { + marginPtr->maxTickWidth = axisPtr->maxTickWidth; + } + if (axisPtr->maxTickHeight > marginPtr->maxTickHeight) { + marginPtr->maxTickHeight = axisPtr->maxTickHeight; + } + } + } + } else { + for (link = Blt_Chain_FirstLink(marginPtr->axes); link != NULL; + link = Blt_Chain_NextLink(link)) { + Axis *axisPtr; + + axisPtr = Blt_Chain_GetValue(link); + if ((axisPtr->flags & (HIDE|AXIS_USE)) == AXIS_USE) { + nVisible++; + if (graphPtr->flags & GET_AXIS_GEOMETRY) { + GetAxisGeometry(graphPtr, axisPtr); + } + if ((axisPtr->titleAlternate) && (l < axisPtr->titleWidth)) { + l = axisPtr->titleWidth; + } + if (isHoriz) { + h += axisPtr->height; + } else { + w += axisPtr->width; + } + if (axisPtr->maxTickWidth > marginPtr->maxTickWidth) { + marginPtr->maxTickWidth = axisPtr->maxTickWidth; + } + if (axisPtr->maxTickHeight > marginPtr->maxTickHeight) { + marginPtr->maxTickHeight = axisPtr->maxTickHeight; + } + } + } + } + /* Enforce a minimum size for margins. */ + if (w < 3) { + w = 3; + } + if (h < 3) { + h = 3; + } + marginPtr->nAxes = nVisible; + marginPtr->axesTitleLength = l; + marginPtr->width = w; + marginPtr->height = h; + marginPtr->axesOffset = (isHoriz) ? h : w; + return marginPtr->axesOffset; +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_LayoutGraph -- + * + * Calculate the layout of the graph. Based upon the data, axis limits, + * X and Y titles, and title height, determine the cavity left which is + * the plotting surface. The first step get the data and axis limits for + * calculating the space needed for the top, bottom, left, and right + * margins. + * + * 1) The LEFT margin is the area from the left border to the Y axis + * (not including ticks). It composes the border width, the width an + * optional Y axis label and its padding, and the tick numeric labels. + * The Y axis label is rotated 90 degrees so that the width is the + * font height. + * + * 2) The RIGHT margin is the area from the end of the graph + * to the right window border. It composes the border width, + * some padding, the font height (this may be dubious. It + * appears to provide a more even border), the max of the + * legend width and 1/2 max X tick number. This last part is + * so that the last tick label is not clipped. + * + * Window Width + * ___________________________________________________________ + * | | | | + * | | TOP height of title | | + * | | | | + * | | x2 title | | + * | | | | + * | | height of x2-axis | | + * |__________|_______________________________|_______________| W + * | | -plotpady | | i + * |__________|_______________________________|_______________| n + * | | top right | | d + * | | | | o + * | LEFT | | RIGHT | w + * | | | | + * | y | Free area = 104% | y2 | H + * | | Plotting surface = 100% | | e + * | t | Tick length = 2 + 2% | t | i + * | i | | i | g + * | t | | t legend| h + * | l | | l width| t + * | e | | e | + * | height| |height | + * | of | | of | + * | y-axis| |y2-axis | + * | | | | + * | |origin 0,0 | | + * |__________|_left_________________bottom___|_______________| + * | |-plotpady | | + * |__________|_______________________________|_______________| + * | | (xoffset, yoffset) | | + * | | | | + * | | height of x-axis | | + * | | | | + * | | BOTTOM x title | | + * |__________|_______________________________|_______________| + * + * 3) The TOP margin is the area from the top window border to the top + * of the graph. It composes the border width, twice the height of + * the title font (if one is given) and some padding between the + * title. + * + * 4) The BOTTOM margin is area from the bottom window border to the + * X axis (not including ticks). It composes the border width, the height + * an optional X axis label and its padding, the height of the font + * of the tick labels. + * + * The plotting area is between the margins which includes the X and Y axes + * including the ticks but not the tick numeric labels. The length of the + * ticks and its padding is 5% of the entire plotting area. Hence the entire + * plotting area is scaled as 105% of the width and height of the area. + * + * The axis labels, ticks labels, title, and legend may or may not be + * displayed which must be taken into account. + * + * if reqWidth > 0 : set outer size + * if reqPlotWidth > 0 : set plot size + *--------------------------------------------------------------------------- + */ +void +Blt_LayoutGraph(Graph *graphPtr) +{ + unsigned int titleY; + unsigned int left, right, top, bottom; + unsigned int plotWidth, plotHeight; + unsigned int inset, inset2; + int width, height; + int pad; + + width = graphPtr->width; + height = graphPtr->height; + + /* + * Step 1: Compute the amount of space needed to display the axes + * associated with each margin. They can be overridden by + * -leftmargin, -rightmargin, -bottommargin, and -topmargin + * graph options, respectively. + */ + left = GetMarginGeometry(graphPtr, &graphPtr->leftMargin); + right = GetMarginGeometry(graphPtr, &graphPtr->rightMargin); + top = GetMarginGeometry(graphPtr, &graphPtr->topMargin); + bottom = GetMarginGeometry(graphPtr, &graphPtr->bottomMargin); + + pad = graphPtr->bottomMargin.maxTickWidth; + if (pad < graphPtr->topMargin.maxTickWidth) { + pad = graphPtr->topMargin.maxTickWidth; + } + pad = pad / 2 + 3; + if (right < pad) { + right = pad; + } + if (left < pad) { + left = pad; + } + pad = graphPtr->leftMargin.maxTickHeight; + if (pad < graphPtr->rightMargin.maxTickHeight) { + pad = graphPtr->rightMargin.maxTickHeight; + } + pad = pad / 2; + if (top < pad) { + top = pad; + } + if (bottom < pad) { + bottom = pad; + } + + if (graphPtr->leftMargin.reqSize > 0) { + left = graphPtr->leftMargin.reqSize; + } + if (graphPtr->rightMargin.reqSize > 0) { + right = graphPtr->rightMargin.reqSize; + } + if (graphPtr->topMargin.reqSize > 0) { + top = graphPtr->topMargin.reqSize; + } + if (graphPtr->bottomMargin.reqSize > 0) { + bottom = graphPtr->bottomMargin.reqSize; + } + + /* + * Step 2: Add the graph title height to the top margin. + */ + if (graphPtr->title != NULL) { + top += graphPtr->titleHeight + 6; + } + inset = (graphPtr->inset + graphPtr->plotBW); + inset2 = 2 * inset; + + /* + * Step 3: Estimate the size of the plot area from the remaining + * space. This may be overridden by the -plotwidth and + * -plotheight graph options. We use this to compute the + * size of the legend. + */ + if (width == 0) { + width = 400; + } + if (height == 0) { + height = 400; + } + plotWidth = (graphPtr->reqPlotWidth > 0) ? graphPtr->reqPlotWidth : + width - (inset2 + left + right); /* Plot width. */ + plotHeight = (graphPtr->reqPlotHeight > 0) ? graphPtr->reqPlotHeight : + height - (inset2 + top + bottom); /* Plot height. */ + Blt_MapLegend(graphPtr, plotWidth, plotHeight); + + /* + * Step 2: Add the legend to the appropiate margin. + */ + if (!Blt_Legend_IsHidden(graphPtr)) { + switch (Blt_Legend_Site(graphPtr)) { + case LEGEND_RIGHT: + right += Blt_Legend_Width(graphPtr) + 2; + break; + case LEGEND_LEFT: + left += Blt_Legend_Width(graphPtr) + 2; + break; + case LEGEND_TOP: + top += Blt_Legend_Height(graphPtr) + 2; + break; + case LEGEND_BOTTOM: + bottom += Blt_Legend_Height(graphPtr) + 2; + break; + case LEGEND_XY: + case LEGEND_PLOT: + case LEGEND_WINDOW: + /* Do nothing. */ + break; + } + } + + /* + * Recompute the plotarea or graph size, now accounting for the legend. + */ + if (graphPtr->reqPlotWidth == 0) { + plotWidth = width - (inset2 + left + right); + if (plotWidth < 1) { + plotWidth = 1; + } + } + if (graphPtr->reqPlotHeight == 0) { + plotHeight = height - (inset2 + top + bottom); + if (plotHeight < 1) { + plotHeight = 1; + } + } + + /* + * Step 5: If necessary, correct for the requested plot area aspect + * ratio. + */ + if ((graphPtr->reqPlotWidth == 0) && (graphPtr->reqPlotHeight == 0) && + (graphPtr->aspect > 0.0f)) { + float ratio; + + /* + * Shrink one dimension of the plotarea to fit the requested + * width/height aspect ratio. + */ + ratio = (float)plotWidth / (float)plotHeight; + if (ratio > graphPtr->aspect) { + int scaledWidth; + + /* Shrink the width. */ + scaledWidth = (int)(plotHeight * graphPtr->aspect); + if (scaledWidth < 1) { + scaledWidth = 1; + } + /* Add the difference to the right margin. */ + /* CHECK THIS: w = scaledWidth; */ + right += (plotWidth - scaledWidth); + } else { + int scaledHeight; + + /* Shrink the height. */ + scaledHeight = (int)(plotWidth / graphPtr->aspect); + if (scaledHeight < 1) { + scaledHeight = 1; + } + /* Add the difference to the top margin. */ + /* CHECK THIS: h = scaledHeight; */ + top += (plotHeight - scaledHeight); + } + } + + /* + * Step 6: If there's multiple axes in a margin, the axis titles will be + * displayed in the adjoining margins. Make sure there's room + * for the longest axis titles. + */ + + if (top < graphPtr->leftMargin.axesTitleLength) { + top = graphPtr->leftMargin.axesTitleLength; + } + if (right < graphPtr->bottomMargin.axesTitleLength) { + right = graphPtr->bottomMargin.axesTitleLength; + } + if (top < graphPtr->rightMargin.axesTitleLength) { + top = graphPtr->rightMargin.axesTitleLength; + } + if (right < graphPtr->topMargin.axesTitleLength) { + right = graphPtr->topMargin.axesTitleLength; + } + + /* + * Step 7: Override calculated values with requested margin sizes. + */ + if (graphPtr->leftMargin.reqSize > 0) { + left = graphPtr->leftMargin.reqSize; + } + if (graphPtr->rightMargin.reqSize > 0) { + right = graphPtr->rightMargin.reqSize; + } + if (graphPtr->topMargin.reqSize > 0) { + top = graphPtr->topMargin.reqSize; + } + if (graphPtr->bottomMargin.reqSize > 0) { + bottom = graphPtr->bottomMargin.reqSize; + } + if (graphPtr->reqPlotWidth > 0) { + int w; + + /* + * Width of plotarea is constained. If there's extra space, add it to + * th left and/or right margins. If there's too little, grow the + * graph width to accomodate it. + */ + w = plotWidth + inset2 + left + right; + if (width > w) { /* Extra space in window. */ + int extra; + + extra = (width - w) / 2; + if (graphPtr->leftMargin.reqSize == 0) { + left += extra; + if (graphPtr->rightMargin.reqSize == 0) { + right += extra; + } else { + left += extra; + } + } else if (graphPtr->rightMargin.reqSize == 0) { + right += extra + extra; + } + } else if (width < w) { + width = w; + } + } + if (graphPtr->reqPlotHeight > 0) { /* Constrain the plotarea height. */ + int h; + + /* + * Height of plotarea is constained. If there's extra space, + * add it to th top and/or bottom margins. If there's too little, + * grow the graph height to accomodate it. + */ + h = plotHeight + inset2 + top + bottom; + if (height > h) { /* Extra space in window. */ + int extra; + + extra = (height - h) / 2; + if (graphPtr->topMargin.reqSize == 0) { + top += extra; + if (graphPtr->bottomMargin.reqSize == 0) { + bottom += extra; + } else { + top += extra; + } + } else if (graphPtr->bottomMargin.reqSize == 0) { + bottom += extra + extra; + } + } else if (height < h) { + height = h; + } + } + graphPtr->width = width; + graphPtr->height = height; + graphPtr->left = left + inset; + graphPtr->top = top + inset; + graphPtr->right = width - right - inset; + graphPtr->bottom = height - bottom - inset; + + graphPtr->leftMargin.width = left + graphPtr->inset; + graphPtr->rightMargin.width = right + graphPtr->inset; + graphPtr->topMargin.height = top + graphPtr->inset; + graphPtr->bottomMargin.height = bottom + graphPtr->inset; + + graphPtr->vOffset = graphPtr->top + graphPtr->padTop; + graphPtr->vRange = plotHeight - PADDING(graphPtr->yPad); + graphPtr->hOffset = graphPtr->left + graphPtr->padLeft; + graphPtr->hRange = plotWidth - PADDING(graphPtr->xPad); + + if (graphPtr->vRange < 1) { + graphPtr->vRange = 1; + } + if (graphPtr->hRange < 1) { + graphPtr->hRange = 1; + } + graphPtr->hScale = 1.0f / (float)graphPtr->hRange; + graphPtr->vScale = 1.0f / (float)graphPtr->vRange; + + /* + * Calculate the placement of the graph title so it is centered within the + * space provided for it in the top margin + */ + titleY = graphPtr->titleHeight; + graphPtr->titleY = 3 + graphPtr->inset; + graphPtr->titleX = (graphPtr->right + graphPtr->left) / 2; + +} + +/* + *--------------------------------------------------------------------------- + * + * ConfigureAxis -- + * + * Configures axis attributes (font, line width, label, etc). + * + * Results: + * The return value is a standard TCL result. + * + * Side Effects: + * Axis layout is deferred until the height and width of the window are + * known. + * + *--------------------------------------------------------------------------- + */ +static int +ConfigureAxis(Axis *axisPtr) +{ + Graph *graphPtr = axisPtr->obj.graphPtr; + float angle; + + /* Check the requested axis limits. Can't allow -min to be greater than + * -max. Do this regardless of -checklimits option. We want to always + * detect when the user has zoomed in beyond the precision of the data.*/ + if (((DEFINED(axisPtr->reqMin)) && (DEFINED(axisPtr->reqMax))) && + (axisPtr->reqMin >= axisPtr->reqMax)) { + char msg[200]; + sprintf_s(msg, 200, + "impossible axis limits (-min %g >= -max %g) for \"%s\"", + axisPtr->reqMin, axisPtr->reqMax, axisPtr->obj.name); + Tcl_AppendResult(graphPtr->interp, msg, (char *)NULL); + return TCL_ERROR; + } + axisPtr->scrollMin = axisPtr->reqScrollMin; + axisPtr->scrollMax = axisPtr->reqScrollMax; + if (axisPtr->logScale) { + if (axisPtr->flags & AXIS_CHECK_LIMITS) { + /* Check that the logscale limits are positive. */ + if ((DEFINED(axisPtr->reqMin)) && (axisPtr->reqMin <= 0.0)) { + Tcl_AppendResult(graphPtr->interp,"bad logscale -min limit \"", + Blt_Dtoa(graphPtr->interp, axisPtr->reqMin), + "\" for axis \"", axisPtr->obj.name, "\"", + (char *)NULL); + return TCL_ERROR; + } + } + if ((DEFINED(axisPtr->scrollMin)) && (axisPtr->scrollMin <= 0.0)) { + axisPtr->scrollMin = Blt_NaN(); + } + if ((DEFINED(axisPtr->scrollMax)) && (axisPtr->scrollMax <= 0.0)) { + axisPtr->scrollMax = Blt_NaN(); + } + } + angle = FMOD(axisPtr->tickAngle, 360.0); + if (angle < 0.0f) { + angle += 360.0f; + } + if (axisPtr->normalBg != NULL) { + Blt_SetBackgroundChangedProc(axisPtr->normalBg, Blt_UpdateGraph, + graphPtr); + } + if (axisPtr->activeBg != NULL) { + Blt_SetBackgroundChangedProc(axisPtr->activeBg, Blt_UpdateGraph, + graphPtr); + } + axisPtr->tickAngle = angle; + ResetTextStyles(axisPtr); + + axisPtr->titleWidth = axisPtr->titleHeight = 0; + if (axisPtr->title != NULL) { + unsigned int w, h; + + Blt_GetTextExtents(axisPtr->titleFont, 0, axisPtr->title, -1, &w, &h); + axisPtr->titleWidth = (unsigned short int)w; + axisPtr->titleHeight = (unsigned short int)h; + } + + /* + * Don't bother to check what configuration options have changed. Almost + * every option changes the size of the plotting area (except for -color + * and -titlecolor), requiring the graph and its contents to be completely + * redrawn. + * + * Recompute the scale and offset of the axis in case -min, -max options + * have changed. + */ + graphPtr->flags |= REDRAW_WORLD; + graphPtr->flags |= MAP_WORLD | RESET_AXES | CACHE_DIRTY; + axisPtr->flags |= DIRTY; + Blt_EventuallyRedrawGraph(graphPtr); + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * NewAxis -- + * + * Create and initialize a structure containing information to display + * a graph axis. + * + * Results: + * The return value is a pointer to an Axis structure. + * + *--------------------------------------------------------------------------- + */ +static Axis * +NewAxis(Graph *graphPtr, const char *name, int margin) +{ + Axis *axisPtr; + Blt_HashEntry *hPtr; + int isNew; + + if (name[0] == '-') { + Tcl_AppendResult(graphPtr->interp, "name of axis \"", name, + "\" can't start with a '-'", (char *)NULL); + return NULL; + } + hPtr = Blt_CreateHashEntry(&graphPtr->axes.table, name, &isNew); + if (!isNew) { + axisPtr = Blt_GetHashValue(hPtr); + if ((axisPtr->flags & DELETE_PENDING) == 0) { + Tcl_AppendResult(graphPtr->interp, "axis \"", name, + "\" already exists in \"", Tk_PathName(graphPtr->tkwin), "\"", + (char *)NULL); + return NULL; + } + axisPtr->flags &= ~DELETE_PENDING; + } else { + axisPtr = Blt_Calloc(1, sizeof(Axis)); + if (axisPtr == NULL) { + Tcl_AppendResult(graphPtr->interp, + "can't allocate memory for axis \"", name, "\"", (char *)NULL); + return NULL; + } + axisPtr->obj.name = Blt_AssertStrdup(name); + axisPtr->hashPtr = hPtr; + Blt_GraphSetObjectClass(&axisPtr->obj, CID_NONE); + axisPtr->obj.graphPtr = graphPtr; + axisPtr->looseMin = axisPtr->looseMax = AXIS_TIGHT; + axisPtr->reqNumMinorTicks = 2; + axisPtr->reqNumMajorTicks = 4 /*10*/; + axisPtr->margin = MARGIN_NONE; + axisPtr->tickLength = 8; + axisPtr->scrollUnits = 10; + axisPtr->reqMin = axisPtr->reqMax = Blt_NaN(); + axisPtr->reqScrollMin = axisPtr->reqScrollMax = Blt_NaN(); + axisPtr->flags = (AXIS_SHOWTICKS|AXIS_GRIDMINOR|AXIS_AUTO_MAJOR| + AXIS_AUTO_MINOR | AXIS_EXTERIOR); + if (graphPtr->classId == CID_ELEM_BAR) { + axisPtr->flags |= AXIS_GRID; + } + if ((graphPtr->classId == CID_ELEM_BAR) && + ((margin == MARGIN_TOP) || (margin == MARGIN_BOTTOM))) { + axisPtr->reqStep = 1.0; + axisPtr->reqNumMinorTicks = 0; + } + if ((margin == MARGIN_RIGHT) || (margin == MARGIN_TOP)) { + axisPtr->flags |= HIDE; + } + Blt_Ts_InitStyle(axisPtr->limitsTextStyle); + axisPtr->tickLabels = Blt_Chain_Create(); + axisPtr->lineWidth = 1; + Blt_SetHashValue(hPtr, axisPtr); + } + return axisPtr; +} + +static int +GetAxisFromObj(Tcl_Interp *interp, Graph *graphPtr, Tcl_Obj *objPtr, + Axis **axisPtrPtr) +{ + Blt_HashEntry *hPtr; + const char *name; + + *axisPtrPtr = NULL; + name = Tcl_GetString(objPtr); + hPtr = Blt_FindHashEntry(&graphPtr->axes.table, name); + if (hPtr != NULL) { + Axis *axisPtr; + + axisPtr = Blt_GetHashValue(hPtr); + if ((axisPtr->flags & DELETE_PENDING) == 0) { + *axisPtrPtr = axisPtr; + return TCL_OK; + } + } + if (interp != NULL) { + Tcl_AppendResult(interp, "can't find axis \"", name, "\" in \"", + Tk_PathName(graphPtr->tkwin), "\"", (char *)NULL); + } + return TCL_ERROR; +} + +static int +GetAxisByClass(Tcl_Interp *interp, Graph *graphPtr, Tcl_Obj *objPtr, + ClassId classId, Axis **axisPtrPtr) +{ + Axis *axisPtr; + + if (GetAxisFromObj(interp, graphPtr, objPtr, &axisPtr) != TCL_OK) { + return TCL_ERROR; + } + if (classId != CID_NONE) { + if ((axisPtr->refCount == 0) || (axisPtr->obj.classId == CID_NONE)) { + /* Set the axis type on the first use of it. */ + Blt_GraphSetObjectClass(&axisPtr->obj, classId); + } else if (axisPtr->obj.classId != classId) { + if (interp != NULL) { + Tcl_AppendResult(interp, "axis \"", Tcl_GetString(objPtr), + "\" is already in use on an opposite ", + axisPtr->obj.className, "-axis", + (char *)NULL); + } + return TCL_ERROR; + } + axisPtr->refCount++; + } + *axisPtrPtr = axisPtr; + return TCL_OK; +} + +void +Blt_DestroyAxes(Graph *graphPtr) +{ + { + Blt_HashEntry *hPtr; + Blt_HashSearch cursor; + + for (hPtr = Blt_FirstHashEntry(&graphPtr->axes.table, &cursor); + hPtr != NULL; hPtr = Blt_NextHashEntry(&cursor)) { + Axis *axisPtr; + + axisPtr = Blt_GetHashValue(hPtr); + axisPtr->hashPtr = NULL; + DestroyAxis(axisPtr); + } + } + Blt_DeleteHashTable(&graphPtr->axes.table); + { + int i; + + for (i = 0; i < 4; i++) { + Blt_Chain_Destroy(graphPtr->axisChain[i]); + } + } + Blt_DeleteHashTable(&graphPtr->axes.tagTable); + Blt_Chain_Destroy(graphPtr->axes.displayList); +} + +void +Blt_ConfigureAxes(Graph *graphPtr) +{ + Blt_HashEntry *hPtr; + Blt_HashSearch cursor; + + for (hPtr = Blt_FirstHashEntry(&graphPtr->axes.table, &cursor); + hPtr != NULL; hPtr = Blt_NextHashEntry(&cursor)) { + Axis *axisPtr; + + axisPtr = Blt_GetHashValue(hPtr); + ConfigureAxis(axisPtr); + } +} + +int +Blt_DefaultAxes(Graph *graphPtr) +{ + int i; + int flags; + + flags = Blt_GraphType(graphPtr); + for (i = 0; i < 4; i++) { + Blt_Chain chain; + Axis *axisPtr; + + chain = Blt_Chain_Create(); + graphPtr->axisChain[i] = chain; + + /* Create a default axis for each chain. */ + axisPtr = NewAxis(graphPtr, axisNames[i].name, i); + if (axisPtr == NULL) { + return TCL_ERROR; + } + axisPtr->refCount = 1; /* Default axes are assumed in use. */ + axisPtr->margin = i; + axisPtr->flags |= AXIS_USE; + Blt_GraphSetObjectClass(&axisPtr->obj, axisNames[i].classId); + /* + * Blt_ConfigureComponentFromObj creates a temporary child window + * by the name of the axis. It's used so that the Tk routines + * that access the X resource database can describe a single + * component and not the entire graph. + */ + if (Blt_ConfigureComponentFromObj(graphPtr->interp, graphPtr->tkwin, + axisPtr->obj.name, "Axis", configSpecs, 0, (Tcl_Obj **)NULL, + (char *)axisPtr, flags) != TCL_OK) { + return TCL_ERROR; + } + if (ConfigureAxis(axisPtr) != TCL_OK) { + return TCL_ERROR; + } + axisPtr->link = Blt_Chain_Append(chain, axisPtr); + axisPtr->chain = chain; + } + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * ActivateOp -- + * + * Activates the axis, drawing the axis with its -activeforeground, + * -activebackgound, -activerelief attributes. + * + * Results: + * A standard TCL result. + * + * Side Effects: + * Graph will be redrawn to reflect the new axis attributes. + * + *--------------------------------------------------------------------------- + */ +static int +ActivateOp(Tcl_Interp *interp, Axis *axisPtr, int objc, Tcl_Obj *const *objv) +{ + Graph *graphPtr = axisPtr->obj.graphPtr; + const char *string; + + string = Tcl_GetString(objv[2]); + if (string[0] == 'a') { + axisPtr->flags |= ACTIVE; + } else { + axisPtr->flags &= ~ACTIVE; + } + if ((axisPtr->flags & (AXIS_USE|HIDE)) == AXIS_USE) { + graphPtr->flags |= DRAW_MARGINS | CACHE_DIRTY; + Blt_EventuallyRedrawGraph(graphPtr); + } + return TCL_OK; +} + +/*------------------------------------------------------------------------------- + * + * BindOp -- + * + * .g axis bind axisName sequence command + * + *--------------------------------------------------------------------------- + */ +static int +BindOp(Tcl_Interp *interp, Axis *axisPtr, int objc, Tcl_Obj *const *objv) +{ + Graph *graphPtr = axisPtr->obj.graphPtr; + + return Blt_ConfigureBindingsFromObj(interp, graphPtr->bindTable, + Blt_MakeAxisTag(graphPtr, axisPtr->obj.name), objc, objv); +} + +/* + *--------------------------------------------------------------------------- + * + * CgetOp -- + * + * Queries axis attributes (font, line width, label, etc). + * + * Results: + * Return value is a standard TCL result. If querying configuration + * values, interp->result will contain the results. + * + *--------------------------------------------------------------------------- + */ +/* ARGSUSED */ +static int +CgetOp(Tcl_Interp *interp, Axis *axisPtr, int objc, Tcl_Obj *const *objv) +{ + Graph *graphPtr = axisPtr->obj.graphPtr; + + return Blt_ConfigureValueFromObj(interp, graphPtr->tkwin, configSpecs, + (char *)axisPtr, objv[0], Blt_GraphType(graphPtr)); +} + +/* + *--------------------------------------------------------------------------- + * + * ConfigureOp -- + * + * Queries or resets axis attributes (font, line width, label, etc). + * + * Results: + * Return value is a standard TCL result. If querying configuration + * values, interp->result will contain the results. + * + * Side Effects: + * Axis resources are possibly allocated (GC, font). Axis layout is + * deferred until the height and width of the window are known. + * + *--------------------------------------------------------------------------- + */ +static int +ConfigureOp(Tcl_Interp *interp, Axis *axisPtr, int objc, Tcl_Obj *const *objv) +{ + Graph *graphPtr = axisPtr->obj.graphPtr; + int flags; + + flags = BLT_CONFIG_OBJV_ONLY | Blt_GraphType(graphPtr); + if (objc == 0) { + return Blt_ConfigureInfoFromObj(interp, graphPtr->tkwin, configSpecs, + (char *)axisPtr, (Tcl_Obj *)NULL, flags); + } else if (objc == 1) { + return Blt_ConfigureInfoFromObj(interp, graphPtr->tkwin, configSpecs, + (char *)axisPtr, objv[0], flags); + } + if (Blt_ConfigureWidgetFromObj(interp, graphPtr->tkwin, configSpecs, + objc, objv, (char *)axisPtr, flags) != TCL_OK) { + return TCL_ERROR; + } + if (ConfigureAxis(axisPtr) != TCL_OK) { + return TCL_ERROR; + } + if (axisPtr->flags & AXIS_USE) { + if (!Blt_ConfigModified(configSpecs, "-*color", "-background", "-bg", + (char *)NULL)) { + graphPtr->flags |= CACHE_DIRTY; + } + Blt_EventuallyRedrawGraph(graphPtr); + } + return TCL_OK; +} + + +/* + *--------------------------------------------------------------------------- + * + * LimitsOp -- + * + * This procedure returns a string representing the axis limits + * of the graph. The format of the string is { left top right bottom}. + * + * Results: + * Always returns TCL_OK. The interp->result field is + * a list of the graph axis limits. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +LimitsOp(Tcl_Interp *interp, Axis *axisPtr, int objc, Tcl_Obj *const *objv) +{ + Graph *graphPtr = axisPtr->obj.graphPtr; + Tcl_Obj *listObjPtr; + double min, max; + + if (graphPtr->flags & RESET_AXES) { + Blt_ResetAxes(graphPtr); + } + if (axisPtr->logScale) { + min = EXP10(axisPtr->axisRange.min); + max = EXP10(axisPtr->axisRange.max); + } else { + min = axisPtr->axisRange.min; + max = axisPtr->axisRange.max; + } + listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL); + Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewDoubleObj(min)); + Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewDoubleObj(max)); + Tcl_SetObjResult(interp, listObjPtr); + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * InvTransformOp -- + * + * Maps the given window coordinate into an axis-value. + * + * Results: + * Returns a standard TCL result. interp->result contains + * the axis value. If an error occurred, TCL_ERROR is returned + * and interp->result will contain an error message. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +InvTransformOp(Tcl_Interp *interp, Axis *axisPtr, int objc, + Tcl_Obj *const *objv) +{ + Graph *graphPtr = axisPtr->obj.graphPtr; + double y; /* Real graph coordinate */ + int sy; /* Integer window coordinate*/ + + if (graphPtr->flags & RESET_AXES) { + Blt_ResetAxes(graphPtr); + } + if (Tcl_GetIntFromObj(interp, objv[0], &sy) != TCL_OK) { + return TCL_ERROR; + } + /* + * Is the axis vertical or horizontal? + * + * Check the site where the axis was positioned. If the axis is + * virtual, all we have to go on is how it was mapped to an + * element (using either -mapx or -mapy options). + */ + if (AxisIsHorizontal(axisPtr)) { + y = Blt_InvHMap(axisPtr, (double)sy); + } else { + y = Blt_InvVMap(axisPtr, (double)sy); + } + Tcl_SetDoubleObj(Tcl_GetObjResult(interp), y); + return TCL_OK; +} + + +/* + *--------------------------------------------------------------------------- + * + * MarginOp -- + * + * This procedure returns a string representing the margin the axis + * resides. The format of the string is { left top right bottom}. + * + * Results: + * Always returns TCL_OK. The interp->result field is + * a list of the graph axis limits. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +MarginOp(Tcl_Interp *interp, Axis *axisPtr, int objc, Tcl_Obj *const *objv) +{ + const char *marginName; + + marginName = ""; + if (axisPtr->flags & AXIS_USE) { + marginName = axisNames[axisPtr->margin].name; + } + Tcl_SetStringObj(Tcl_GetObjResult(interp), marginName, -1); + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * TransformOp -- + * + * Maps the given axis-value to a window coordinate. + * + * Results: + * Returns a standard TCL result. interp->result contains + * the window coordinate. If an error occurred, TCL_ERROR + * is returned and interp->result will contain an error + * message. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +TransformOp(Tcl_Interp *interp, Axis *axisPtr, int objc, Tcl_Obj *const *objv) +{ + Graph *graphPtr = axisPtr->obj.graphPtr; + double x; + + if (graphPtr->flags & RESET_AXES) { + Blt_ResetAxes(graphPtr); + } + if (Blt_ExprDoubleFromObj(interp, objv[0], &x) != TCL_OK) { + return TCL_ERROR; + } + if (AxisIsHorizontal(axisPtr)) { + x = Blt_HMap(axisPtr, x); + } else { + x = Blt_VMap(axisPtr, x); + } + Tcl_SetIntObj(Tcl_GetObjResult(interp), (int)x); + return TCL_OK; +} + + +/* + *--------------------------------------------------------------------------- + * + * TypeOp -- + * + * This procedure returns a string representing the margin the axis + * resides. The format of the string is "x", "y", or "". + * + * Results: + * Always returns TCL_OK. The interp->result field is the type of + * axis. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +TypeOp(Tcl_Interp *interp, Axis *axisPtr, int objc, Tcl_Obj *const *objv) +{ + const char *typeName; + + typeName = ""; + if (axisPtr->flags & AXIS_USE) { + if (axisNames[axisPtr->margin].classId == CID_AXIS_X) { + typeName = "x"; + } else if (axisNames[axisPtr->margin].classId == CID_AXIS_Y) { + typeName = "y"; + } + } + Tcl_SetStringObj(Tcl_GetObjResult(interp), typeName, -1); + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * UseOp -- + * + * Sets the default axis for a margin. + * + * Results: + * A standard TCL result. If the named axis doesn't exist + * an error message is put in interp->result. + * + * .g xaxis use "abc def gah" + * .g xaxis use [lappend abc [.g axis use]] + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +UseOp(Tcl_Interp *interp, Axis *axisPtr, int objc, Tcl_Obj *const *objv) +{ + Graph *graphPtr = (Graph *)axisPtr; + Blt_Chain chain; + Blt_ChainLink link; + Tcl_Obj **axisObjv; + ClassId classId; + int axisObjc; + int i; + + chain = graphPtr->margins[lastMargin].axes; + if (objc == 0) { + Tcl_Obj *listObjPtr; + + listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL); + for (link = Blt_Chain_FirstLink(chain); link != NULL; + link = Blt_Chain_NextLink(link)) { + Axis *axisPtr; + + axisPtr = Blt_Chain_GetValue(link); + Tcl_ListObjAppendElement(interp, listObjPtr, + Tcl_NewStringObj(axisPtr->obj.name, -1)); + } + Tcl_SetObjResult(interp, listObjPtr); + return TCL_OK; + } + if ((lastMargin == MARGIN_BOTTOM) || (lastMargin == MARGIN_TOP)) { + classId = (graphPtr->inverted) ? CID_AXIS_Y : CID_AXIS_X; + } else { + classId = (graphPtr->inverted) ? CID_AXIS_X : CID_AXIS_Y; + } + if (Tcl_ListObjGetElements(interp, objv[0], &axisObjc, &axisObjv) + != TCL_OK) { + return TCL_ERROR; + } + for (link = Blt_Chain_FirstLink(chain); link!= NULL; + link = Blt_Chain_NextLink(link)) { + Axis *axisPtr; + + axisPtr = Blt_Chain_GetValue(link); + axisPtr->link = NULL; + axisPtr->flags &= ~AXIS_USE; + /* Clear the axis type if it's not currently used.*/ + if (axisPtr->refCount == 0) { + Blt_GraphSetObjectClass(&axisPtr->obj, CID_NONE); + } + } + Blt_Chain_Reset(chain); + for (i = 0; i < axisObjc; i++) { + Axis *axisPtr; + + if (GetAxisFromObj(interp, graphPtr, axisObjv[i], &axisPtr) != TCL_OK){ + return TCL_ERROR; + } + if (axisPtr->obj.classId == CID_NONE) { + Blt_GraphSetObjectClass(&axisPtr->obj, classId); + } else if (axisPtr->obj.classId != classId) { + Tcl_AppendResult(interp, "wrong type axis \"", + axisPtr->obj.name, "\": can't use ", + axisPtr->obj.className, " type axis.", (char *)NULL); + return TCL_ERROR; + } + if (axisPtr->link != NULL) { + /* Move the axis from the old margin's "use" list to the new. */ + Blt_Chain_UnlinkLink(axisPtr->chain, axisPtr->link); + Blt_Chain_AppendLink(chain, axisPtr->link); + } else { + axisPtr->link = Blt_Chain_Append(chain, axisPtr); + } + axisPtr->chain = chain; + axisPtr->flags |= AXIS_USE; + } + graphPtr->flags |= (GET_AXIS_GEOMETRY | LAYOUT_NEEDED | RESET_AXES); + /* When any axis changes, we need to layout the entire graph. */ + graphPtr->flags |= (MAP_WORLD | REDRAW_WORLD); + Blt_EventuallyRedrawGraph(graphPtr); + return TCL_OK; +} + +static int +ViewOp(Tcl_Interp *interp, Axis *axisPtr, int objc, Tcl_Obj *const *objv) +{ + Graph *graphPtr; + double axisOffset, axisScale; + double fract; + double viewMin, viewMax, worldMin, worldMax; + double viewWidth, worldWidth; + + graphPtr = axisPtr->obj.graphPtr; + worldMin = axisPtr->valueRange.min; + worldMax = axisPtr->valueRange.max; + /* Override data dimensions with user-selected limits. */ + if (DEFINED(axisPtr->scrollMin)) { + worldMin = axisPtr->scrollMin; + } + if (DEFINED(axisPtr->scrollMax)) { + worldMax = axisPtr->scrollMax; + } + viewMin = axisPtr->min; + viewMax = axisPtr->max; + /* Bound the view within scroll region. */ + if (viewMin < worldMin) { + viewMin = worldMin; + } + if (viewMax > worldMax) { + viewMax = worldMax; + } + if (axisPtr->logScale) { + worldMin = log10(worldMin); + worldMax = log10(worldMax); + viewMin = log10(viewMin); + viewMax = log10(viewMax); + } + worldWidth = worldMax - worldMin; + viewWidth = viewMax - viewMin; + + /* Unlike horizontal axes, vertical axis values run opposite of the + * scrollbar first/last values. So instead of pushing the axis minimum + * around, we move the maximum instead. */ + if (AxisIsHorizontal(axisPtr) != axisPtr->descending) { + axisOffset = viewMin - worldMin; + axisScale = graphPtr->hScale; + } else { + axisOffset = worldMax - viewMax; + axisScale = graphPtr->vScale; + } + if (objc == 4) { + Tcl_Obj *listObjPtr; + double first, last; + + first = Clamp(axisOffset / worldWidth); + last = Clamp((axisOffset + viewWidth) / worldWidth); + listObjPtr = Tcl_NewListObj(0, NULL); + Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewDoubleObj(first)); + Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewDoubleObj(last)); + Tcl_SetObjResult(interp, listObjPtr); + return TCL_OK; + } + fract = axisOffset / worldWidth; + if (GetAxisScrollInfo(interp, objc, objv, &fract, + viewWidth / worldWidth, axisPtr->scrollUnits, axisScale) != TCL_OK) { + return TCL_ERROR; + } + if (AxisIsHorizontal(axisPtr) != axisPtr->descending) { + axisPtr->reqMin = (fract * worldWidth) + worldMin; + axisPtr->reqMax = axisPtr->reqMin + viewWidth; + } else { + axisPtr->reqMax = worldMax - (fract * worldWidth); + axisPtr->reqMin = axisPtr->reqMax - viewWidth; + } + if (axisPtr->logScale) { + axisPtr->reqMin = EXP10(axisPtr->reqMin); + axisPtr->reqMax = EXP10(axisPtr->reqMax); + } + graphPtr->flags |= (GET_AXIS_GEOMETRY | LAYOUT_NEEDED | RESET_AXES); + Blt_EventuallyRedrawGraph(graphPtr); + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * AxisCreateOp -- + * + * Creates a new axis. + * + * Results: + * Returns a standard TCL result. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +AxisCreateOp(Tcl_Interp *interp, Graph *graphPtr, int objc, + Tcl_Obj *const *objv) +{ + Axis *axisPtr; + int flags; + + axisPtr = NewAxis(graphPtr, Tcl_GetString(objv[3]), MARGIN_NONE); + if (axisPtr == NULL) { + return TCL_ERROR; + } + flags = Blt_GraphType(graphPtr); + if ((Blt_ConfigureComponentFromObj(interp, graphPtr->tkwin, + axisPtr->obj.name, "Axis", configSpecs, objc - 4, objv + 4, + (char *)axisPtr, flags) != TCL_OK) || + (ConfigureAxis(axisPtr) != TCL_OK)) { + DestroyAxis(axisPtr); + return TCL_ERROR; + } + Tcl_SetStringObj(Tcl_GetObjResult(interp), axisPtr->obj.name, -1); + return TCL_OK; +} +/* + *--------------------------------------------------------------------------- + * + * AxisActivateOp -- + * + * Activates the axis, drawing the axis with its -activeforeground, + * -activebackgound, -activerelief attributes. + * + * Results: + * A standard TCL result. + * + * Side Effects: + * Graph will be redrawn to reflect the new axis attributes. + * + *--------------------------------------------------------------------------- + */ +static int +AxisActivateOp(Tcl_Interp *interp, Graph *graphPtr, int objc, + Tcl_Obj *const *objv) +{ + Axis *axisPtr; + + if (GetAxisFromObj(interp, graphPtr, objv[3], &axisPtr) != TCL_OK) { + return TCL_ERROR; + } + return ActivateOp(interp, axisPtr, objc, objv); +} + + +/*------------------------------------------------------------------------------- + * + * AxisBindOp -- + * + * .g axis bind axisName sequence command + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +AxisBindOp(Tcl_Interp *interp, Graph *graphPtr, int objc, + Tcl_Obj *const *objv) +{ + if (objc == 3) { + Blt_HashEntry *hPtr; + Blt_HashSearch cursor; + Tcl_Obj *listObjPtr; + + listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL); + for (hPtr = Blt_FirstHashEntry(&graphPtr->axes.tagTable, &cursor); + hPtr != NULL; hPtr = Blt_NextHashEntry(&cursor)) { + const char *tagName; + Tcl_Obj *objPtr; + + tagName = Blt_GetHashKey(&graphPtr->axes.tagTable, hPtr); + objPtr = Tcl_NewStringObj(tagName, -1); + Tcl_ListObjAppendElement(interp, listObjPtr, objPtr); + } + Tcl_SetObjResult(interp, listObjPtr); + return TCL_OK; + } + return Blt_ConfigureBindingsFromObj(interp, graphPtr->bindTable, + Blt_MakeAxisTag(graphPtr, Tcl_GetString(objv[3])), objc - 4, objv + 4); +} + + +/* + *--------------------------------------------------------------------------- + * + * AxisCgetOp -- + * + * Queries axis attributes (font, line width, label, etc). + * + * Results: + * Return value is a standard TCL result. If querying configuration + * values, interp->result will contain the results. + * + *--------------------------------------------------------------------------- + */ +/* ARGSUSED */ +static int +AxisCgetOp(Tcl_Interp *interp, Graph *graphPtr, int objc, Tcl_Obj *const *objv) +{ + Axis *axisPtr; + + if (GetAxisFromObj(interp, graphPtr, objv[3], &axisPtr) != TCL_OK) { + return TCL_ERROR; + } + return CgetOp(interp, axisPtr, objc - 4, objv + 4); +} + +/* + *--------------------------------------------------------------------------- + * + * AxisConfigureOp -- + * + * Queries or resets axis attributes (font, line width, label, etc). + * + * Results: + * Return value is a standard TCL result. If querying configuration + * values, interp->result will contain the results. + * + * Side Effects: + * Axis resources are possibly allocated (GC, font). Axis layout is + * deferred until the height and width of the window are known. + * + *--------------------------------------------------------------------------- + */ +static int +AxisConfigureOp(Tcl_Interp *interp, Graph *graphPtr, int objc, + Tcl_Obj *const *objv) +{ + Tcl_Obj *const *options; + int i; + int nNames, nOpts; + + /* Figure out where the option value pairs begin */ + objc -= 3; + objv += 3; + for (i = 0; i < objc; i++) { + Axis *axisPtr; + const char *string; + + string = Tcl_GetString(objv[i]); + if (string[0] == '-') { + break; + } + if (GetAxisFromObj(interp, graphPtr, objv[i], &axisPtr) != TCL_OK) { + return TCL_ERROR; + } + } + nNames = i; /* Number of pen names specified */ + nOpts = objc - i; /* Number of options specified */ + options = objv + i; /* Start of options in objv */ + + for (i = 0; i < nNames; i++) { + Axis *axisPtr; + + if (GetAxisFromObj(interp, graphPtr, objv[i], &axisPtr) != TCL_OK) { + return TCL_ERROR; + } + if (ConfigureOp(interp, axisPtr, nOpts, options) != TCL_OK) { + break; + } + } + if (i < nNames) { + return TCL_ERROR; + } + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * AxisDeleteOp -- + * + * Deletes one or more axes. The actual removal may be deferred until the + * axis is no longer used by any element. The axis can't be referenced by + * its name any longer and it may be recreated. + * + * Results: + * Returns a standard TCL result. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +AxisDeleteOp(Tcl_Interp *interp, Graph *graphPtr, int objc, + Tcl_Obj *const *objv) +{ + int i; + + for (i = 3; i < objc; i++) { + Axis *axisPtr; + + if (GetAxisFromObj(interp, graphPtr, objv[i], &axisPtr) != TCL_OK) { + return TCL_ERROR; + } + axisPtr->flags |= DELETE_PENDING; + if (axisPtr->refCount == 0) { + Tcl_EventuallyFree(axisPtr, FreeAxis); + } + } + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * AxisFocusOp -- + * + * Activates the axis, drawing the axis with its -activeforeground, + * -activebackgound, -activerelief attributes. + * + * Results: + * A standard TCL result. + * + * Side Effects: + * Graph will be redrawn to reflect the new axis attributes. + * + *--------------------------------------------------------------------------- + */ +static int +AxisFocusOp(Tcl_Interp *interp, Graph *graphPtr, int objc, Tcl_Obj *const *objv) +{ + if (objc > 3) { + Axis *axisPtr; + const char *string; + + axisPtr = NULL; + string = Tcl_GetString(objv[3]); + if ((string[0] != '\0') && + (GetAxisFromObj(interp, graphPtr, objv[3], &axisPtr) != TCL_OK)) { + return TCL_ERROR; + } + graphPtr->focusPtr = NULL; + if ((axisPtr != NULL) && + ((axisPtr->flags & (AXIS_USE|HIDE)) == AXIS_USE)) { + graphPtr->focusPtr = axisPtr; + } + Blt_SetFocusItem(graphPtr->bindTable, graphPtr->focusPtr, NULL); + } + /* Return the name of the axis that has focus. */ + if (graphPtr->focusPtr != NULL) { + Tcl_SetStringObj(Tcl_GetObjResult(interp), + graphPtr->focusPtr->obj.name, -1); + } + return TCL_OK; +} + + +/* + *--------------------------------------------------------------------------- + * + * AxisGetOp -- + * + * Returns the name of the picked axis (using the axis bind operation). + * Right now, the only name accepted is "current". + * + * Results: + * A standard TCL result. The interpreter result will contain the name of + * the axis. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +AxisGetOp(Tcl_Interp *interp, Graph *graphPtr, int objc, Tcl_Obj *const *objv) +{ + Axis *axisPtr; + + axisPtr = Blt_GetCurrentItem(graphPtr->bindTable); + /* Report only on axes. */ + if ((axisPtr != NULL) && + ((axisPtr->obj.classId == CID_AXIS_X) || + (axisPtr->obj.classId == CID_AXIS_Y) || + (axisPtr->obj.classId == CID_NONE))) { + char c; + char *string; + + string = Tcl_GetString(objv[3]); + c = string[0]; + if ((c == 'c') && (strcmp(string, "current") == 0)) { + Tcl_SetStringObj(Tcl_GetObjResult(interp), axisPtr->obj.name,-1); + } else if ((c == 'd') && (strcmp(string, "detail") == 0)) { + Tcl_SetStringObj(Tcl_GetObjResult(interp), axisPtr->detail, -1); + } + } + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * AxisInvTransformOp -- + * + * Maps the given window coordinate into an axis-value. + * + * Results: + * Returns a standard TCL result. interp->result contains + * the axis value. If an error occurred, TCL_ERROR is returned + * and interp->result will contain an error message. + * + *--------------------------------------------------------------------------- + */ +static int +AxisInvTransformOp(Tcl_Interp *interp, Graph *graphPtr, int objc, + Tcl_Obj *const *objv) +{ + Axis *axisPtr; + + if (GetAxisFromObj(interp, graphPtr, objv[3], &axisPtr) != TCL_OK) { + return TCL_ERROR; + } + return InvTransformOp(interp, axisPtr, objc - 4, objv + 4); +} + +/* + *--------------------------------------------------------------------------- + * + * AxisLimitsOp -- + * + * This procedure returns a string representing the axis limits of the + * graph. The format of the string is { left top right bottom}. + * + * Results: + * Always returns TCL_OK. The interp->result field is + * a list of the graph axis limits. + * + *--------------------------------------------------------------------------- + */ +static int +AxisLimitsOp(Tcl_Interp *interp, Graph *graphPtr, int objc, + Tcl_Obj *const *objv) +{ + Axis *axisPtr; + + if (GetAxisFromObj(interp, graphPtr, objv[3], &axisPtr) != TCL_OK) { + return TCL_ERROR; + } + return LimitsOp(interp, axisPtr, objc - 4, objv + 4); +} + +/* + *--------------------------------------------------------------------------- + * + * AxisMarginOp -- + * + * This procedure returns a string representing the axis limits of the + * graph. The format of the string is { left top right bottom}. + * + * Results: + * Always returns TCL_OK. The interp->result field is + * a list of the graph axis limits. + * + *--------------------------------------------------------------------------- + */ +static int +AxisMarginOp(Tcl_Interp *interp, Graph *graphPtr, int objc, + Tcl_Obj *const *objv) +{ + Axis *axisPtr; + + if (GetAxisFromObj(interp, graphPtr, objv[3], &axisPtr) != TCL_OK) { + return TCL_ERROR; + } + return MarginOp(interp, axisPtr, objc - 4, objv + 4); +} + + +/* + *--------------------------------------------------------------------------- + * + * AxisNamesOp -- + * + * Return a list of the names of all the axes. + * + * Results: + * Returns a standard TCL result. + * + *--------------------------------------------------------------------------- + */ + +/*ARGSUSED*/ +static int +AxisNamesOp(Tcl_Interp *interp, Graph *graphPtr, int objc, Tcl_Obj *const *objv) +{ + Tcl_Obj *listObjPtr; + + listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL); + if (objc == 3) { + Blt_HashEntry *hPtr; + Blt_HashSearch cursor; + + for (hPtr = Blt_FirstHashEntry(&graphPtr->axes.table, &cursor); + hPtr != NULL; hPtr = Blt_NextHashEntry(&cursor)) { + Axis *axisPtr; + + axisPtr = Blt_GetHashValue(hPtr); + if (axisPtr->flags & DELETE_PENDING) { + continue; + } + Tcl_ListObjAppendElement(interp, listObjPtr, + Tcl_NewStringObj(axisPtr->obj.name, -1)); + } + } else { + Blt_HashEntry *hPtr; + Blt_HashSearch cursor; + + for (hPtr = Blt_FirstHashEntry(&graphPtr->axes.table, &cursor); + hPtr != NULL; hPtr = Blt_NextHashEntry(&cursor)) { + Axis *axisPtr; + int i; + + axisPtr = Blt_GetHashValue(hPtr); + for (i = 3; i < objc; i++) { + const char *pattern; + + pattern = Tcl_GetString(objv[i]); + if (Tcl_StringMatch(axisPtr->obj.name, pattern)) { + Tcl_ListObjAppendElement(interp, listObjPtr, + Tcl_NewStringObj(axisPtr->obj.name, -1)); + break; + } + } + } + } + Tcl_SetObjResult(interp, listObjPtr); + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * AxisTransformOp -- + * + * Maps the given axis-value to a window coordinate. + * + * Results: + * Returns the window coordinate via interp->result. If an error occurred, + * TCL_ERROR is returned and interp->result will contain an error message. + * + *--------------------------------------------------------------------------- + */ +static int +AxisTransformOp(Tcl_Interp *interp, Graph *graphPtr, int objc, + Tcl_Obj *const *objv) +{ + Axis *axisPtr; + + if (GetAxisFromObj(interp, graphPtr, objv[3], &axisPtr) != TCL_OK) { + return TCL_ERROR; + } + return TransformOp(interp, axisPtr, objc - 4, objv + 4); +} + +/* + *--------------------------------------------------------------------------- + * + * AxisMarginOp -- + * + * This procedure returns a string representing the axis limits of the + * graph. The format of the string is { left top right bottom}. + * + * Results: + * Always returns TCL_OK. The interp->result field is + * a list of the graph axis limits. + * + *--------------------------------------------------------------------------- + */ +static int +AxisTypeOp(Tcl_Interp *interp, Graph *graphPtr, int objc, + Tcl_Obj *const *objv) +{ + Axis *axisPtr; + + if (GetAxisFromObj(interp, graphPtr, objv[3], &axisPtr) != TCL_OK) { + return TCL_ERROR; + } + return TypeOp(interp, axisPtr, objc - 4, objv + 4); +} + + +static int +AxisViewOp(Tcl_Interp *interp, Graph *graphPtr, int objc, Tcl_Obj *const *objv) +{ + Axis *axisPtr; + + if (GetAxisFromObj(interp, graphPtr, objv[3], &axisPtr) != TCL_OK) { + return TCL_ERROR; + } + return ViewOp(interp, axisPtr, objc - 4, objv + 4); +} + +static Blt_OpSpec virtAxisOps[] = { + {"activate", 1, AxisActivateOp, 4, 4, "axisName"}, + {"bind", 1, AxisBindOp, 3, 6, "axisName sequence command"}, + {"cget", 2, AxisCgetOp, 5, 5, "axisName option"}, + {"configure", 2, AxisConfigureOp, 4, 0, "axisName ?axisName?... " + "?option value?..."}, + {"create", 2, AxisCreateOp, 4, 0, "axisName ?option value?..."}, + {"deactivate", 3, AxisActivateOp, 4, 4, "axisName"}, + {"delete", 3, AxisDeleteOp, 3, 0, "?axisName?..."}, + {"focus", 1, AxisFocusOp, 3, 4, "?axisName?"}, + {"get", 1, AxisGetOp, 4, 4, "name"}, + {"invtransform", 1, AxisInvTransformOp, 5, 5, "axisName value"}, + {"limits", 1, AxisLimitsOp, 4, 4, "axisName"}, + {"margin", 1, AxisMarginOp, 4, 4, "axisName"}, + {"names", 1, AxisNamesOp, 3, 0, "?pattern?..."}, + {"transform", 2, AxisTransformOp, 5, 5, "axisName value"}, + {"type", 2, AxisTypeOp, 4, 4, "axisName"}, + {"view", 1, AxisViewOp, 4, 7, "axisName ?moveto fract? " + "?scroll number what?"}, +}; +static int nVirtAxisOps = sizeof(virtAxisOps) / sizeof(Blt_OpSpec); + +int +Blt_VirtualAxisOp(Graph *graphPtr, Tcl_Interp *interp, int objc, + Tcl_Obj *const *objv) +{ + GraphVirtualAxisProc *proc; + int result; + + proc = Blt_GetOpFromObj(interp, nVirtAxisOps, virtAxisOps, BLT_OP_ARG2, + objc, objv, 0); + if (proc == NULL) { + return TCL_ERROR; + } + result = (*proc) (interp, graphPtr, objc, objv); + return result; +} + +static Blt_OpSpec axisOps[] = { + {"activate", 1, ActivateOp, 3, 3, "",}, + {"bind", 1, BindOp, 2, 5, "sequence command",}, + {"cget", 2, CgetOp, 4, 4, "option",}, + {"configure", 2, ConfigureOp, 3, 0, "?option value?...",}, + {"deactivate", 1, ActivateOp, 3, 3, "",}, + {"invtransform", 1, InvTransformOp, 4, 4, "value",}, + {"limits", 1, LimitsOp, 3, 3, "",}, + {"transform", 1, TransformOp, 4, 4, "value",}, + {"use", 1, UseOp, 3, 4, "?axisName?",}, + {"view", 1, ViewOp, 3, 6, "?moveto fract? ",}, +}; + +static int nAxisOps = sizeof(axisOps) / sizeof(Blt_OpSpec); + +int +Blt_AxisOp(Tcl_Interp *interp, Graph *graphPtr, int margin, int objc, + Tcl_Obj *const *objv) +{ + int result; + GraphAxisProc *proc; + + proc = Blt_GetOpFromObj(interp, nAxisOps, axisOps, BLT_OP_ARG2, + objc, objv, 0); + if (proc == NULL) { + return TCL_ERROR; + } + if (proc == UseOp) { + lastMargin = margin; /* Set global variable to the margin + * in the argument list. Needed only + * for UseOp. */ + result = (*proc)(interp, (Axis *)graphPtr, objc - 3, objv + 3); + } else { + Axis *axisPtr; + + axisPtr = Blt_GetFirstAxis(graphPtr->margins[margin].axes); + if (axisPtr == NULL) { + return TCL_OK; + } + result = (*proc)(interp, axisPtr, objc - 3, objv + 3); + } + return result; +} + +void +Blt_MapAxes(Graph *graphPtr) +{ + int margin; + + for (margin = 0; margin < 4; margin++) { + Blt_Chain chain; + Blt_ChainLink link; + int count, offset; + + chain = graphPtr->margins[margin].axes; + count = offset = 0; + for (link = Blt_Chain_FirstLink(chain); link != NULL; + link = Blt_Chain_NextLink(link)) { + Axis *axisPtr; + + axisPtr = Blt_Chain_GetValue(link); + if ((axisPtr->flags & (AXIS_USE|DELETE_PENDING)) != AXIS_USE) { + continue; + } + if (graphPtr->stackAxes) { + if (axisPtr->reqNumMajorTicks <= 0) { + axisPtr->reqNumMajorTicks = 4; + } + MapStackedAxis(axisPtr, count, margin); + } else { + if (axisPtr->reqNumMajorTicks <= 0) { + axisPtr->reqNumMajorTicks = 4; + } + MapAxis(axisPtr, offset, margin); + } + if (axisPtr->flags & AXIS_GRID) { + MapGridlines(axisPtr); + } + offset += (AxisIsHorizontal(axisPtr)) + ? axisPtr->height : axisPtr->width; + count++; + } + } +} + +void +Blt_DrawAxes(Graph *graphPtr, Drawable drawable) +{ + int i; + + for (i = 0; i < 4; i++) { + Blt_ChainLink link; + + for (link = Blt_Chain_LastLink(graphPtr->margins[i].axes); + link != NULL; link = Blt_Chain_PrevLink(link)) { + Axis *axisPtr; + + axisPtr = Blt_Chain_GetValue(link); + if ((axisPtr->flags & (DELETE_PENDING|HIDE|AXIS_USE)) == AXIS_USE) { + DrawAxis(axisPtr, drawable); + } + } + } +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_DrawGrids -- + * + * Draws the grid lines associated with each axis. + * + * Results: + * None. + * + *--------------------------------------------------------------------------- + */ +void +Blt_DrawGrids(Graph *graphPtr, Drawable drawable) +{ + int i; + + for (i = 0; i < 4; i++) { + Blt_ChainLink link; + + for (link = Blt_Chain_FirstLink(graphPtr->margins[i].axes); link != NULL; + link = Blt_Chain_NextLink(link)) { + Axis *axisPtr; + + axisPtr = Blt_Chain_GetValue(link); + if (axisPtr->flags & (DELETE_PENDING|HIDE)) { + continue; + } + if ((axisPtr->flags & AXIS_USE) && (axisPtr->flags & AXIS_GRID)) { + Blt_Draw2DSegments(graphPtr->display, drawable, + axisPtr->major.gc, axisPtr->major.segments, + axisPtr->major.nUsed); + if (axisPtr->flags & AXIS_GRIDMINOR) { + Blt_Draw2DSegments(graphPtr->display, drawable, + axisPtr->minor.gc, axisPtr->minor.segments, + axisPtr->minor.nUsed); + } + } + } + } +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_GridsToPostScript -- + * + * Draws the grid lines associated with each axis. + * + * Results: + * None. + * + *--------------------------------------------------------------------------- + */ +void +Blt_GridsToPostScript(Graph *graphPtr, Blt_Ps ps) +{ + int i; + + for (i = 0; i < 4; i++) { + Blt_ChainLink link; + + for (link = Blt_Chain_FirstLink(graphPtr->margins[i].axes); link != NULL; + link = Blt_Chain_NextLink(link)) { + Axis *axisPtr; + + axisPtr = Blt_Chain_GetValue(link); + if ((axisPtr->flags & (DELETE_PENDING|HIDE|AXIS_USE|AXIS_GRID)) != + (AXIS_GRID|AXIS_USE)) { + continue; + } + Blt_Ps_Format(ps, "%% Axis %s: grid line attributes\n", + axisPtr->obj.name); + Blt_Ps_XSetLineAttributes(ps, axisPtr->major.color, + axisPtr->major.lineWidth, &axisPtr->major.dashes, CapButt, + JoinMiter); + Blt_Ps_Format(ps, "%% Axis %s: major grid line segments\n", + axisPtr->obj.name); + Blt_Ps_Draw2DSegments(ps, axisPtr->major.segments, + axisPtr->major.nUsed); + if (axisPtr->flags & AXIS_GRIDMINOR) { + Blt_Ps_XSetLineAttributes(ps, axisPtr->minor.color, + axisPtr->minor.lineWidth, &axisPtr->minor.dashes, CapButt, + JoinMiter); + Blt_Ps_Format(ps, "%% Axis %s: minor grid line segments\n", + axisPtr->obj.name); + Blt_Ps_Draw2DSegments(ps, axisPtr->minor.segments, + axisPtr->minor.nUsed); + } + } + } +} + +void +Blt_AxesToPostScript(Graph *graphPtr, Blt_Ps ps) +{ + Margin *mp, *mend; + + for (mp = graphPtr->margins, mend = mp + 4; mp < mend; mp++) { + Blt_ChainLink link; + + for (link = Blt_Chain_FirstLink(mp->axes); link != NULL; + link = Blt_Chain_NextLink(link)) { + Axis *axisPtr; + + axisPtr = Blt_Chain_GetValue(link); + if ((axisPtr->flags & (DELETE_PENDING|HIDE|AXIS_USE)) == AXIS_USE) { + AxisToPostScript(ps, axisPtr); + } + } + } +} + + +/* + *--------------------------------------------------------------------------- + * + * Blt_DrawAxisLimits -- + * + * Draws the min/max values of the axis in the plotting area. The text + * strings are formatted according to the "sprintf" format descriptors in + * the limitsFormats array. + * + * Results: + * None. + * + * Side Effects: + * Draws the numeric values of the axis limits into the outer regions of + * the plotting area. + * + *--------------------------------------------------------------------------- + */ +void +Blt_DrawAxisLimits(Graph *graphPtr, Drawable drawable) +{ + Blt_HashEntry *hPtr; + Blt_HashSearch cursor; + char minString[200], maxString[200]; + int vMin, hMin, vMax, hMax; + +#define SPACING 8 + vMin = vMax = graphPtr->left + graphPtr->padLeft + 2; + hMin = hMax = graphPtr->bottom - graphPtr->padBottom - 2; /* Offsets */ + + for (hPtr = Blt_FirstHashEntry(&graphPtr->axes.table, &cursor); + hPtr != NULL; hPtr = Blt_NextHashEntry(&cursor)) { + Axis *axisPtr; + Dim2D textDim; + const char *minFmt, *maxFmt; + char *minPtr, *maxPtr; + int isHoriz; + + axisPtr = Blt_GetHashValue(hPtr); + if (axisPtr->flags & DELETE_PENDING) { + continue; + } + if (axisPtr->nFormats == 0) { + continue; + } + isHoriz = AxisIsHorizontal(axisPtr); + minPtr = maxPtr = NULL; + minFmt = maxFmt = axisPtr->limitsFormats[0]; + if (axisPtr->nFormats > 1) { + maxFmt = axisPtr->limitsFormats[1]; + } + if (minFmt[0] != '\0') { + minPtr = minString; + sprintf_s(minString, 200, minFmt, axisPtr->axisRange.min); + } + if (maxFmt[0] != '\0') { + maxPtr = maxString; + sprintf_s(maxString, 200, maxFmt, axisPtr->axisRange.max); + } + if (axisPtr->descending) { + char *tmp; + + tmp = minPtr, minPtr = maxPtr, maxPtr = tmp; + } + if (maxPtr != NULL) { + if (isHoriz) { + Blt_Ts_SetAngle(axisPtr->limitsTextStyle, 90.0); + Blt_Ts_SetAnchor(axisPtr->limitsTextStyle, TK_ANCHOR_SE); + Blt_DrawText2(graphPtr->tkwin, drawable, maxPtr, + &axisPtr->limitsTextStyle, graphPtr->right, hMax, &textDim); + hMax -= (textDim.height + SPACING); + } else { + Blt_Ts_SetAngle(axisPtr->limitsTextStyle, 0.0); + Blt_Ts_SetAnchor(axisPtr->limitsTextStyle, TK_ANCHOR_NW); + Blt_DrawText2(graphPtr->tkwin, drawable, maxPtr, + &axisPtr->limitsTextStyle, vMax, graphPtr->top, &textDim); + vMax += (textDim.width + SPACING); + } + } + if (minPtr != NULL) { + Blt_Ts_SetAnchor(axisPtr->limitsTextStyle, TK_ANCHOR_SW); + if (isHoriz) { + Blt_Ts_SetAngle(axisPtr->limitsTextStyle, 90.0); + Blt_DrawText2(graphPtr->tkwin, drawable, minPtr, + &axisPtr->limitsTextStyle, graphPtr->left, hMin, &textDim); + hMin -= (textDim.height + SPACING); + } else { + Blt_Ts_SetAngle(axisPtr->limitsTextStyle, 0.0); + Blt_DrawText2(graphPtr->tkwin, drawable, minPtr, + &axisPtr->limitsTextStyle, vMin, graphPtr->bottom, &textDim); + vMin += (textDim.width + SPACING); + } + } + } /* Loop on axes */ +} + +void +Blt_AxisLimitsToPostScript(Graph *graphPtr, Blt_Ps ps) +{ + Blt_HashEntry *hPtr; + Blt_HashSearch cursor; + double vMin, hMin, vMax, hMax; + char string[200]; + +#define SPACING 8 + vMin = vMax = graphPtr->left + graphPtr->padLeft + 2; + hMin = hMax = graphPtr->bottom - graphPtr->padBottom - 2; /* Offsets */ + for (hPtr = Blt_FirstHashEntry(&graphPtr->axes.table, &cursor); + hPtr != NULL; hPtr = Blt_NextHashEntry(&cursor)) { + Axis *axisPtr; + const char *minFmt, *maxFmt; + unsigned int textWidth, textHeight; + + axisPtr = Blt_GetHashValue(hPtr); + if (axisPtr->flags & DELETE_PENDING) { + continue; + } + if (axisPtr->nFormats == 0) { + continue; + } + minFmt = maxFmt = axisPtr->limitsFormats[0]; + if (axisPtr->nFormats > 1) { + maxFmt = axisPtr->limitsFormats[1]; + } + if (*maxFmt != '\0') { + sprintf_s(string, 200, maxFmt, axisPtr->axisRange.max); + Blt_GetTextExtents(axisPtr->tickFont, 0, string, -1, &textWidth, + &textHeight); + if ((textWidth > 0) && (textHeight > 0)) { + if (axisPtr->obj.classId == CID_AXIS_X) { + Blt_Ts_SetAngle(axisPtr->limitsTextStyle, 90.0); + Blt_Ts_SetAnchor(axisPtr->limitsTextStyle, TK_ANCHOR_SE); + Blt_Ps_DrawText(ps, string, &axisPtr->limitsTextStyle, + (double)graphPtr->right, hMax); + hMax -= (textWidth + SPACING); + } else { + Blt_Ts_SetAngle(axisPtr->limitsTextStyle, 0.0); + Blt_Ts_SetAnchor(axisPtr->limitsTextStyle, TK_ANCHOR_NW); + Blt_Ps_DrawText(ps, string, &axisPtr->limitsTextStyle, + vMax, (double)graphPtr->top); + vMax += (textWidth + SPACING); + } + } + } + if (*minFmt != '\0') { + sprintf_s(string, 200, minFmt, axisPtr->axisRange.min); + Blt_GetTextExtents(axisPtr->tickFont, 0, string, -1, &textWidth, + &textHeight); + if ((textWidth > 0) && (textHeight > 0)) { + Blt_Ts_SetAnchor(axisPtr->limitsTextStyle, TK_ANCHOR_SW); + if (axisPtr->obj.classId == CID_AXIS_X) { + Blt_Ts_SetAngle(axisPtr->limitsTextStyle, 90.0); + Blt_Ps_DrawText(ps, string, &axisPtr->limitsTextStyle, + (double)graphPtr->left, hMin); + hMin -= (textWidth + SPACING); + } else { + Blt_Ts_SetAngle(axisPtr->limitsTextStyle, 0.0); + Blt_Ps_DrawText(ps, string, &axisPtr->limitsTextStyle, + vMin, (double)graphPtr->bottom); + vMin += (textWidth + SPACING); + } + } + } + } +} + +Axis * +Blt_GetFirstAxis(Blt_Chain chain) +{ + Blt_ChainLink link; + + link = Blt_Chain_FirstLink(chain); + if (link == NULL) { + return NULL; + } + return Blt_Chain_GetValue(link); +} + +Axis * +Blt_NearestAxis(Graph *graphPtr, int x, int y) +{ + Blt_HashEntry *hPtr; + Blt_HashSearch cursor; + + for (hPtr = Blt_FirstHashEntry(&graphPtr->axes.table, &cursor); + hPtr != NULL; hPtr = Blt_NextHashEntry(&cursor)) { + Axis *axisPtr; + + axisPtr = Blt_GetHashValue(hPtr); + if ((axisPtr->flags & (DELETE_PENDING|HIDE|AXIS_USE)) != AXIS_USE) { + continue; + } + if (axisPtr->flags & AXIS_SHOWTICKS) { + Blt_ChainLink link; + + for (link = Blt_Chain_FirstLink(axisPtr->tickLabels); link != NULL; + link = Blt_Chain_NextLink(link)) { + TickLabel *labelPtr; + Point2d t; + double rw, rh; + Point2d bbox[5]; + + labelPtr = Blt_Chain_GetValue(link); + Blt_GetBoundingBox(labelPtr->width, labelPtr->height, + axisPtr->tickAngle, &rw, &rh, bbox); + t = Blt_AnchorPoint(labelPtr->anchorPos.x, + labelPtr->anchorPos.y, rw, rh, axisPtr->tickAnchor); + t.x = x - t.x - (rw * 0.5); + t.y = y - t.y - (rh * 0.5); + + bbox[4] = bbox[0]; + if (Blt_PointInPolygon(&t, bbox, 5)) { + axisPtr->detail = "label"; + return axisPtr; + } + } + } + if (axisPtr->title != NULL) { /* and then the title string. */ + Point2d bbox[5]; + Point2d t; + double rw, rh; + unsigned int w, h; + + Blt_GetTextExtents(axisPtr->titleFont, 0, axisPtr->title,-1,&w,&h); + Blt_GetBoundingBox(w, h, axisPtr->titleAngle, &rw, &rh, bbox); + t = Blt_AnchorPoint(axisPtr->titlePos.x, axisPtr->titlePos.y, + rw, rh, axisPtr->titleAnchor); + /* Translate the point so that the 0,0 is the upper left + * corner of the bounding box. */ + t.x = x - t.x - (rw * 0.5); + t.y = y - t.y - (rh * 0.5); + + bbox[4] = bbox[0]; + if (Blt_PointInPolygon(&t, bbox, 5)) { + axisPtr->detail = "title"; + return axisPtr; + } + } + if (axisPtr->lineWidth > 0) { /* Check for the axis region */ + if ((x <= axisPtr->right) && (x >= axisPtr->left) && + (y <= axisPtr->bottom) && (y >= axisPtr->top)) { + axisPtr->detail = "line"; + return axisPtr; + } + } + } + return NULL; +} + +ClientData +Blt_MakeAxisTag(Graph *graphPtr, const char *tagName) +{ + Blt_HashEntry *hPtr; + int isNew; + + hPtr = Blt_CreateHashEntry(&graphPtr->axes.tagTable, tagName, &isNew); + return Blt_GetHashKey(&graphPtr->axes.tagTable, hPtr); +} + + + +/* + *--------------------------------------------------------------------------- + * + * TimeScaleAxis -- + * + * Determine the units of a linear scaled axis. + * + * The axis limits are either the range of the data values mapped + * to the axis (autoscaled), or the values specified by the -min + * and -max options (manual). + * + * If autoscaled, the smallest and largest major ticks will + * encompass the range of data values. If the -loose option is + * selected, the next outer ticks are choosen. If tight, the + * ticks are at or inside of the data limits are used. + * + * If manually set, the ticks are at or inside the data limits + * are used. This makes sense for zooming. You want the + * selected range to represent the next limit, not something a + * bit bigger. + * + * Note: I added an "always" value to the -loose option to force + * the manually selected axes to be loose. It's probably + * not a good idea. + * + * maxY + * | units = magnitude (of least significant digit) + * | high = largest unit tick < max axis value + * high _| low = smallest unit tick > min axis value + * | + * | range = high - low + * | # ticks = greatest factor of range/units + * _| + * U | + * n | + * i | + * t _| + * | + * | + * | + * low _| + * | + * |_minX________________maxX__ + * | | | | | + * minY low high + * minY + * + * numTicks = Number of ticks + * min = Minimum value of axis + * max = Maximum value of axis + * range = Range of values (max - min) + * + * Results: + * None. + * + * Side Effects: + * The axis tick information is set. The actual tick values will + * be generated later. + * + *--------------------------------------------------------------------------- + */ +static void +TimeScaleAxis(Axis *axisPtr, double min, double max) +{ +#ifdef notdef + double step; + double tickMin, tickMax; + double axisMin, axisMax; + unsigned int nTicks; + + range = max - min; + + +#define SECONDS_MIN (60) +#define SECONDS_HOUR (SECONDS_MIN * 60) +#define SECONDS_DAY (SECONDS_HOUR * 24) +#define SECONDS_MONTH (SECONDS_DAY * 30) +#define SECONDS_YEAR (SECONDS_MONTH * 12) + div = nTicks - 1; + if (range > (SECONDS_MONTH * 6 * div)) { + unit = TICK_YEAR; + } else if (range > (SECONDS_MONTH * 3 * div)) { + unit = TICKS_6MONTH; + first = timefloor(min, unit); + last = timeceil(max, unit); + } else if (range > (SECONDS_MONTH * 2 * div)) { + unit = TICKS_3MONTH; + first = timefloor(min, unit); + last = timeceil(max, unit); + } else if (range > (SECONDS_MONTH * div)) { + unit = TICKS_2MONTH; + first = timefloor(min, unit); + last = timeceil(max, unit); + } else if (range > (SECONDS_DAY * 15 * div)) { + unit = TICKS_1MONTH; + } else if (range > (SECONDS_DAY * 10 * div)) { + unit = TICKS_15DAY; + } else if (range > (SECONDS_DAY * div)) { + unit = TICKS_10DAY; + } else if (range > (SECONDS_HOUR * 12 * div)) { + unit = TICKS_1DAY; + } else if (range > (SECONDS_HOUR * 6 * div)) { + unit = TICKS_12HOUR; + } else if (range > (SECONDS_HOUR * 3 * div)) { + unit = TICKS_6HOUR; + } else if (range > (SECONDS_HOUR * div)) { + unit = TICKS_3HOUR; + } else if (range > (SECONDS_MIN * 30 * div)) { + unit = TICKS_HOUR; + } else if (range > (SECONDS_MIN * 20 * div)) { + unit = TICKS_30MIN; + } else if (range > (SECONDS_MIN * 15 * div)) { + unit = TICKS_20MIN; + } else if (range > (SECONDS_MIN * 10 * div)) { + unit = TICKS_15MIN; + } else if (range > (SECONDS_MIN * div)) { + unit = TICKS_10MIN; + } else if (range > (SECONDS_SEC * 30 * div)) { + unit = TICKS_1MIN; + } else if (range > (SECONDS_SEC * 20 * div)) { + unit = TICKS_30SEC; + } else if (range > (SECONDS_SEC * 15 * div)) { + unit = TICKS_20SEC; + } else if (range > (SECONDS_SEC * 10 * div)) { + unit = TICKS_15SEC; + } else if (range > (SECONDS_SEC * div)) { + unit = TICKS_10SEC; + } else { + unit = TICKS_1SEC; + } + + } else { + unit = TICKS_SECS; + } + nTicks = 0; + step = 1.0; + /* Suppress compiler warning. */ + axisMin = axisMax = tickMin = tickMax = Blt_NaN(); + if (min < max) { + double range; + + range = max - min; + /* Calculate the major tick stepping. */ + if (axisPtr->reqStep > 0.0) { + /* An interval was designated by the user. Keep scaling it until + * it fits comfortably within the current range of the axis. */ + step = axisPtr->reqStep; + while ((2 * step) >= range) { + step *= 0.5; + } + } else { + range = NiceNum(range, 0); + step = NiceNum(range / axisPtr->reqNumMajorTicks, 1); + } + + /* Find the outer tick values. Add 0.0 to prevent getting -0.0. */ + axisMin = tickMin = floor(min / step) * step + 0.0; + axisMax = tickMax = ceil(max / step) * step + 0.0; + + nTicks = Round((tickMax - tickMin) / step) + 1; + } + axisPtr->majorSweep.step = step; + axisPtr->majorSweep.initial = tickMin; + axisPtr->majorSweep.nSteps = nTicks; + + /* + * The limits of the axis are either the range of the data ("tight") or at + * the next outer tick interval ("loose"). The looseness or tightness has + * to do with how the axis fits the range of data values. This option is + * overridden when the user sets an axis limit (by either -min or -max + * option). The axis limit is always at the selected limit (otherwise we + * assume that user would have picked a different number). + */ + if ((axisPtr->looseMin == AXIS_TIGHT) || + ((axisPtr->looseMin == AXIS_LOOSE) && + (DEFINED(axisPtr->reqMin)))) { + axisMin = min; + } + if ((axisPtr->looseMax == AXIS_TIGHT) || + ((axisPtr->looseMax == AXIS_LOOSE) && + (DEFINED(axisPtr->reqMax)))) { + axisMax = max; + } + SetAxisRange(&axisPtr->axisRange, axisMin, axisMax); + + /* Now calculate the minor tick step and number. */ + + if ((axisPtr->reqNumMinorTicks > 0) && (axisPtr->flags & AXIS_AUTO_MAJOR)) { + nTicks = axisPtr->reqNumMinorTicks - 1; + step = 1.0 / (nTicks + 1); + } else { + nTicks = 0; /* No minor ticks. */ + step = 0.5; /* Don't set the minor tick interval to + * 0.0. It makes the GenerateTicks + * routine * create minor log-scale tick + * marks. */ + } + axisPtr->minorSweep.initial = axisPtr->minorSweep.step = step; + axisPtr->minorSweep.nSteps = nTicks; +#endif +} + +#ifdef notdef +static Ticks * +TimeGenerateTicks(TickSweep *sweepPtr) +{ + Ticks *ticksPtr; + + ticksPtr = Blt_AssertMalloc(sizeof(Ticks) + + (sweepPtr->nSteps * sizeof(double))); + ticksPtr->nTicks = 0; + + if (sweepPtr->step == 0.0) { + /* Hack: A zero step indicates to use log values. */ + int i; + /* Precomputed log10 values [1..10] */ + static double logTable[] = { + 0.0, + 0.301029995663981, + 0.477121254719662, + 0.602059991327962, + 0.698970004336019, + 0.778151250383644, + 0.845098040014257, + 0.903089986991944, + 0.954242509439325, + 1.0 + }; + for (i = 0; i < sweepPtr->nSteps; i++) { + ticksPtr->values[i] = logTable[i]; + } + } else { + double value; + int i; + + value = sweepPtr->initial; /* Start from smallest axis tick */ + for (i = 0; i < sweepPtr->nSteps; i++) { + value = UROUND(value, sweepPtr->step); + ticksPtr->values[i] = value; + value += sweepPtr->step; + } + } + ticksPtr->nTicks = sweepPtr->nSteps; + return ticksPtr; +} + +static double +TimeFloor(double min, int unit) +{ + unsigned long ticks; + + ticks = (long)floor(min); + localtime_r(&ticks, &tm); + switch(unit) { + case TICK_6MONTHS: + tm.sec = 0; + tm.min = 0; + tm.day = 0; + tm. + } +} +static double +TimeCeil(double max, int unit) +{ + +} + +#endif + diff --git a/src/bltGrAxis.h b/src/bltGrAxis.h new file mode 100644 index 0000000..a009aec --- /dev/null +++ b/src/bltGrAxis.h @@ -0,0 +1,332 @@ + +/* + * bltGrAxis.h -- + * + * Copyright 1993-2004 George A Howlett. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY + * KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS + * OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef _BLT_GR_AXIS_H +#define _BLT_GR_AXIS_H + +#include "bltList.h" + +/* + *--------------------------------------------------------------------------- + * + * Grid -- + * + * Contains attributes of describing how to draw grids (at major ticks) + * in the graph. Grids may be mapped to either/both X and Y axis. + * + *--------------------------------------------------------------------------- + */ +typedef struct { + Blt_Dashes dashes; /* Dash style of the grid. This represents an + * array of alternatingly drawn pixel + * values. */ + int lineWidth; /* Width of the grid lines */ + XColor *color; /* Color of the grid lines */ + GC gc; /* Graphics context for the grid. */ + + Segment2d *segments; /* Array of line segments representing the + * grid lines */ + int nUsed; /* # of axis segments in use. */ + int nAllocated; /* # of axis segments allocated. */ +} Grid; + +/* + *--------------------------------------------------------------------------- + * + * AxisRange -- + * + * Designates a range of values by a minimum and maximum limit. + * + *--------------------------------------------------------------------------- + */ +typedef struct { + double min, max, range, scale; +} AxisRange; + +/* + *--------------------------------------------------------------------------- + * + * TickLabel -- + * + * Structure containing the X-Y screen coordinates of the tick + * label (anchored at its center). + * + *--------------------------------------------------------------------------- + */ +typedef struct { + Point2d anchorPos; + unsigned int width, height; + char string[1]; +} TickLabel; + +/* + *--------------------------------------------------------------------------- + * + * Ticks -- + * + * Structure containing information where the ticks (major or + * minor) will be displayed on the graph. + * + *--------------------------------------------------------------------------- + */ +typedef struct { + unsigned int nTicks; /* # of ticks on axis */ + double values[1]; /* Array of tick values (malloc-ed). */ +} Ticks; + +/* + *--------------------------------------------------------------------------- + * + * TickSweep -- + * + * Structure containing information where the ticks (major or + * minor) will be displayed on the graph. + * + *--------------------------------------------------------------------------- + */ +typedef struct { + double initial; /* Initial value */ + double step; /* Size of interval */ + unsigned int nSteps; /* Number of intervals. */ +} TickSweep; + +/* + *--------------------------------------------------------------------------- + * + * Axis -- + * + * Structure contains options controlling how the axis will be + * displayed. + * + *--------------------------------------------------------------------------- + */ +typedef struct { + GraphObj obj; /* Must be first field in axis. */ + + unsigned int flags; + + Blt_HashEntry *hashPtr; + + /* Fields specific to axes. */ + + const char *detail; + + int refCount; /* Number of elements referencing this + * axis. */ + int logScale; /* If non-zero, generate log scale + * ticks for the axis. */ + int timeScale; /* If non-zero, generate time scale + * ticks for the axis. This option is + * overridden by -logscale. */ + int descending; /* If non-zero, display the range of + * values on the axis in descending + * order, from high to low. */ + + int looseMin, looseMax; /* If non-zero, axis range extends to + * the outer major ticks, otherwise at + * the limits of the data values. This + * is overriddened by setting the -min + * and -max options. */ + + const char *title; /* Title of the axis. */ + + int titleAlternate; /* Indicates whether to position the + * title above/left of the axis. */ + + Point2d titlePos; /* Position of the title */ + + unsigned short int titleWidth, titleHeight; + + + int lineWidth; /* Width of lines representing axis + * (including ticks). If zero, then + * no axis lines or ticks are + * drawn. */ + + const char **limitsFormats; /* One or two strings of sprintf-like + * formats describing how to display + * virtual axis limits. If NULL, + * display no limits. */ + int nFormats; + + TextStyle limitsTextStyle; /* Text attributes (color, font, + * rotation, etc.) of the limits. */ + + double windowSize; /* Size of a sliding window of values + * used to scale the axis + * automatically as new data values + * are added. The axis will always + * display the latest values in this + * range. */ + + double shiftBy; /* Shift maximum by this interval. */ + + int tickLength; /* Length of major ticks in pixels */ + + const char *formatCmd; /* Specifies a TCL command, to be + * invoked by the axis whenever it has + * to generate tick labels. */ + + Tcl_Obj *scrollCmdObjPtr; + int scrollUnits; + + double min, max; /* The actual axis range. */ + + double reqMin, reqMax; /* Requested axis bounds. Consult the + * axisPtr->flags field for + * AXIS_CONFIG_MIN and AXIS_CONFIG_MAX + * to see if the requested bound have + * been set. They override the + * computed range of the axis + * (determined by auto-scaling). */ + + double reqScrollMin, reqScrollMax; + + double scrollMin, scrollMax; /* Defines the scrolling reqion of the + * axis. Normally the region is + * determined from the data limits. If + * specified, these values override + * the data-range. */ + + AxisRange valueRange; /* Range of data values of elements + * mapped to this axis. This is used + * to auto-scale the axis in "tight" + * mode. */ + AxisRange axisRange; /* Smallest and largest major tick + * values for the axis. The tick + * values lie outside the range of + * data values. This is used to + * auto-scale the axis in "loose" + * mode. */ + + double prevMin, prevMax; + + double reqStep; /* If > 0.0, overrides the computed major + * tick interval. Otherwise a stepsize + * is automatically calculated, based + * upon the range of elements mapped to the + * axis. The default value is 0.0. */ + + Ticks *t1Ptr; /* Array of major tick positions. May be + * set by the user or generated from the + * major sweep below. */ + + Ticks *t2Ptr; /* Array of minor tick positions. May be + * set by the user or generated from the + * minor sweep below. */ + + TickSweep minorSweep, majorSweep; + + int reqNumMajorTicks; /* Default number of ticks to be displayed. */ + int reqNumMinorTicks; /* If non-zero, represents the + * requested the number of minor ticks + * to be uniformally displayed along + * each major tick. */ + + + int labelOffset; /* If non-zero, indicates that the tick + * label should be offset to sit in the + * middle of the next interval. */ + + /* The following fields are specific to logical axes */ + + int margin; /* Margin that contains this axis. */ + Blt_ChainLink link; /* Axis link in margin list. */ + Blt_Chain chain; + Segment2d *segments; /* Array of line segments representing + * the major and minor ticks, but also + * the * axis line itself. The segment + * coordinates * are relative to the + * axis. */ + int nSegments; /* Number of segments in the above + * array. */ + Blt_Chain tickLabels; /* Contains major tick label strings + * and their offsets along the + * axis. */ + short int left, right, top, bottom; /* Region occupied by the of axis. */ + short int width, height; /* Extents of axis */ + short int maxTickWidth, maxTickHeight; + Blt_Background normalBg; + Blt_Background activeBg; + XColor *activeFgColor; + + int relief; + int borderWidth; + int activeRelief; + + float tickAngle; + Blt_Font tickFont; + Tk_Anchor tickAnchor; + Tk_Anchor reqTickAnchor; + XColor *tickColor; + GC tickGC; /* Graphics context for axis and tick + * labels */ + GC activeTickGC; + + double titleAngle; + Blt_Font titleFont; + Tk_Anchor titleAnchor; + Tk_Justify titleJustify; + XColor *titleColor; + + Grid major, minor; /* Axis grid information. */ + + double screenScale; + int screenMin, screenRange; + +} Axis; + +/* + *--------------------------------------------------------------------------- + * + * Axis2d -- + * + * The pair of axes mapping a point onto the graph. + * + *--------------------------------------------------------------------------- + */ +typedef struct { + Axis *x, *y; +} Axis2d; + +/* Axis flags: */ + +#define AXIS_AUTO_MAJOR (1<<16) /* Auto-generate major ticks. */ +#define AXIS_AUTO_MINOR (1<<17) /* Auto-generate minor ticks. */ +#define AXIS_ONSCREEN (1<<18) /* Axis is displayed on the screen via + * the "use" operation */ +#define AXIS_GRID (1<<19) +#define AXIS_GRID_MINOR (1<<20) +#define AXIS_TICKS (1<<21) +#define AXIS_TICKS_INTERIOR (1<<22) +#define AXIS_CHECK_LIMITS (1<<23) +#define AXIS_LOGSCALE (1<<24) +#define AXIS_DECREASING (1<<25) + +#endif /* _BLT_GR_AXIS_H */ diff --git a/src/bltGrBind.C b/src/bltGrBind.C new file mode 100644 index 0000000..7b288f7 --- /dev/null +++ b/src/bltGrBind.C @@ -0,0 +1,1825 @@ + +/* + * bltBind.c -- + * + * This module implements object binding procedures for the BLT toolkit. + * + * Copyright 1998 George A Howlett. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include "bltInt.h" +#include "bltBind.h" +#include <bltList.h> + +static Tk_EventProc BindProc; + +typedef struct _Blt_BindTable BindTable; + +/* Make button presses on objects have implicit grab. */ +#define FULLY_SIMULATE_GRAB 1 + +/* + * Binding table procedures. + */ +#define REPICK_IN_PROGRESS (1<<0) +#define LEFT_GRABBED_ITEM (1<<1) + +#define ALL_BUTTONS_MASK \ + (Button1Mask | Button2Mask | Button3Mask | Button4Mask | Button5Mask) + +#ifndef VirtualEventMask +#define VirtualEventMask (1L << 30) +#endif + +#define ALL_VALID_EVENTS_MASK \ + (ButtonMotionMask | Button1MotionMask | Button2MotionMask | \ + Button3MotionMask | Button4MotionMask | Button5MotionMask | \ + ButtonPressMask | ButtonReleaseMask | EnterWindowMask | \ + LeaveWindowMask | KeyPressMask | KeyReleaseMask | \ + PointerMotionMask | VirtualEventMask) + +static int buttonMasks[] = +{ + 0, /* No buttons pressed */ + Button1Mask, Button2Mask, Button3Mask, Button4Mask, Button5Mask, +}; + + +/* + * How to make drag&drop work? + * + * Right now we generate pseudo <Enter> <Leave> events within button grab + * on an object. They're marked NotifyVirtual instead of NotifyAncestor. + * A better solution: generate new-style virtual <<DragEnter>> + * <<DragMotion>> <<DragLeave>> events. These virtual events don't have + * to exist as "real" event sequences, like virtual events do now. + */ + +/* + *--------------------------------------------------------------------------- + * + * DoEvent -- + * + * This procedure is called to invoke binding processing for a new event + * that is associated with the current item for a legend. + * + * Results: + * None. + * + * Side effects: + * Depends on the bindings for the legend. A binding script could delete + * an entry, so callers should protect themselves with Tcl_Preserve and + * Tcl_Release. + * + *--------------------------------------------------------------------------- + */ +static void +DoEvent( + BindTable *bindPtr, /* Binding information for widget in which + * event occurred. */ + XEvent *eventPtr, /* Real or simulated X event that is to be + * processed. */ + ClientData item, /* Item picked. */ + ClientData context) /* Context of item. */ +{ + Blt_List tagList; + + if ((bindPtr->tkwin == NULL) || (bindPtr->bindingTable == NULL)) { + return; + } + if ((eventPtr->type == KeyPress) || (eventPtr->type == KeyRelease)) { + item = bindPtr->focusItem; + context = bindPtr->focusContext; + } + if (item == NULL) { + return; + } + /* + * Invoke the binding system. + */ + tagList = Blt_List_Create(BLT_ONE_WORD_KEYS); + if (bindPtr->tagProc == NULL) { + Blt_List_Append(tagList, Tk_GetUid("all"), 0); + Blt_List_Append(tagList, (char *)item, 0); + } else { + (*bindPtr->tagProc) (bindPtr, item, context, tagList); + } + if (Blt_List_GetLength(tagList) > 0) { + int nTags; + ClientData *tagArray; +#define MAX_STATIC_TAGS 64 + ClientData staticTags[MAX_STATIC_TAGS]; + Blt_ListNode node; + + tagArray = staticTags; + nTags = Blt_List_GetLength(tagList); + if (nTags >= MAX_STATIC_TAGS) { + tagArray = Blt_AssertMalloc(sizeof(ClientData) * nTags); + + } + nTags = 0; + for (node = Blt_List_FirstNode(tagList); node != NULL; + node = Blt_List_NextNode(node)) { + tagArray[nTags++] = (ClientData)Blt_List_GetKey(node); + } + Tk_BindEvent(bindPtr->bindingTable, eventPtr, bindPtr->tkwin, nTags, + tagArray); + if (tagArray != staticTags) { + Blt_Free(tagArray); + } + } + Blt_List_Destroy(tagList); +} + +/* + *--------------------------------------------------------------------------- + * + * PickCurrentItem -- + * + * Find the topmost item in a legend that contains a given location and + * mark the the current item. If the current item has changed, generate + * a fake exit event on the old current item and a fake enter event on + * the new current item. + * + * Results: + * None. + * + * Side effects: + * The current item may change. If it does, then the commands associated + * with item entry and exit could do just about anything. A binding + * script could delete the legend, so callers should protect themselves + * with Tcl_Preserve and Tcl_Release. + * + *--------------------------------------------------------------------------- + */ +static void +PickCurrentItem( + BindTable *bindPtr, /* Binding table information. */ + XEvent *eventPtr) /* Event describing location of mouse cursor. + * Must be EnterWindow, LeaveWindow, + * ButtonRelease, or MotionNotify. */ +{ + int buttonDown; + ClientData newItem, oldItem; + ClientData newContext; + + /* + * Check whether or not a button is down. If so, we'll log entry and exit + * into and out of the current item, but not entry into any other item. + * This implements a form of grabbing equivalent to what the X server does + * for windows. + */ + buttonDown = (bindPtr->state & ALL_BUTTONS_MASK); + if (!buttonDown) { + bindPtr->flags &= ~LEFT_GRABBED_ITEM; + } + + /* + * Save information about this event in the widget. The event in the + * widget is used for two purposes: + * + * 1. Event bindings: if the current item changes, fake events are + * generated to allow item-enter and item-leave bindings to trigger. + * 2. Reselection: if the current item gets deleted, can use the + * saved event to find a new current item. + * Translate MotionNotify events into EnterNotify events, since that's + * what gets reported to item handlers. + */ + + if (eventPtr != &bindPtr->pickEvent) { + if ((eventPtr->type == MotionNotify) || + (eventPtr->type == ButtonRelease)) { + bindPtr->pickEvent.xcrossing.type = EnterNotify; + bindPtr->pickEvent.xcrossing.serial = eventPtr->xmotion.serial; + bindPtr->pickEvent.xcrossing.send_event = + eventPtr->xmotion.send_event; + bindPtr->pickEvent.xcrossing.display = eventPtr->xmotion.display; + bindPtr->pickEvent.xcrossing.window = eventPtr->xmotion.window; + bindPtr->pickEvent.xcrossing.root = eventPtr->xmotion.root; + bindPtr->pickEvent.xcrossing.subwindow = None; + bindPtr->pickEvent.xcrossing.time = eventPtr->xmotion.time; + bindPtr->pickEvent.xcrossing.x = eventPtr->xmotion.x; + bindPtr->pickEvent.xcrossing.y = eventPtr->xmotion.y; + bindPtr->pickEvent.xcrossing.x_root = eventPtr->xmotion.x_root; + bindPtr->pickEvent.xcrossing.y_root = eventPtr->xmotion.y_root; + bindPtr->pickEvent.xcrossing.mode = NotifyNormal; + bindPtr->pickEvent.xcrossing.detail = NotifyNonlinear; + bindPtr->pickEvent.xcrossing.same_screen + = eventPtr->xmotion.same_screen; + bindPtr->pickEvent.xcrossing.focus = False; + bindPtr->pickEvent.xcrossing.state = eventPtr->xmotion.state; + } else { + bindPtr->pickEvent = *eventPtr; + } + } + bindPtr->activePick = TRUE; + + /* + * If this is a recursive call (there's already a partially completed call + * pending on the stack; it's in the middle of processing a Leave event + * handler for the old current item) then just return; the pending call + * will do everything that's needed. + */ + if (bindPtr->flags & REPICK_IN_PROGRESS) { + return; + } + /* + * A LeaveNotify event automatically means that there's no current item, + * so the check for closest item can be skipped. + */ + newContext = NULL; + if (bindPtr->pickEvent.type != LeaveNotify) { + int x, y; + + x = bindPtr->pickEvent.xcrossing.x; + y = bindPtr->pickEvent.xcrossing.y; + newItem = (*bindPtr->pickProc) (bindPtr->clientData, x, y, &newContext); + } else { + newItem = NULL; + } + if (((newItem == bindPtr->currentItem) && + (newContext == bindPtr->currentContext)) && + ((bindPtr->flags & LEFT_GRABBED_ITEM) == 0)) { + /* + * Nothing to do: the current item hasn't changed. + */ + return; + } +#if FULLY_SIMULATE_GRAB + if (((newItem != bindPtr->currentItem) || + (newContext != bindPtr->currentContext)) && + (buttonDown)) { + bindPtr->flags |= LEFT_GRABBED_ITEM; +#ifdef notdef + fprintf(stderr, "pickcurrentitem: simulate grab.\n"); +#endif + return; + } +#endif + /* + * Simulate a LeaveNotify event on the previous current item and an + * EnterNotify event on the new current item. Remove the "current" tag + * from the previous current item and place it on the new current item. + */ + oldItem = bindPtr->currentItem; + Tcl_Preserve(oldItem); + Tcl_Preserve(newItem); + + if ((bindPtr->currentItem != NULL) && + ((newItem != bindPtr->currentItem) || + (newContext != bindPtr->currentContext)) && + ((bindPtr->flags & LEFT_GRABBED_ITEM) == 0)) { + XEvent event; + + event = bindPtr->pickEvent; + event.type = LeaveNotify; + /* + * If the event's detail happens to be NotifyInferior the binding + * mechanism will discard the event. To be consistent, always use + * NotifyAncestor. + */ + event.xcrossing.detail = NotifyAncestor; + + bindPtr->flags |= REPICK_IN_PROGRESS; + DoEvent(bindPtr, &event, bindPtr->currentItem, bindPtr->currentContext); + bindPtr->flags &= ~REPICK_IN_PROGRESS; + + /* + * Note: during DoEvent above, it's possible that bindPtr->newItem got + * reset to NULL because the item was deleted. + */ + } + if (((newItem != bindPtr->currentItem) || + (newContext != bindPtr->currentContext)) && + (buttonDown)) { + XEvent event; + + bindPtr->flags |= LEFT_GRABBED_ITEM; + event = bindPtr->pickEvent; + if ((newItem != bindPtr->newItem) || + (newContext != bindPtr->newContext)) { + ClientData savedItem; + ClientData savedContext; + + /* + * Generate <Enter> and <Leave> events for objects during button + * grabs. This isn't standard. But for example, it allows one to + * provide balloon help on the individual entries of the Hierbox + * widget. + */ + savedItem = bindPtr->currentItem; + savedContext = bindPtr->currentContext; + if (bindPtr->newItem != NULL) { + event.type = LeaveNotify; + event.xcrossing.detail = NotifyVirtual /* Ancestor */ ; + bindPtr->currentItem = bindPtr->newItem; + DoEvent(bindPtr, &event, bindPtr->newItem, bindPtr->newContext); + } + bindPtr->newItem = newItem; + bindPtr->newContext = newContext; + if (newItem != NULL) { + event.type = EnterNotify; + event.xcrossing.detail = NotifyVirtual /* Ancestor */ ; + bindPtr->currentItem = newItem; + DoEvent(bindPtr, &event, newItem, newContext); + } + bindPtr->currentItem = savedItem; + bindPtr->currentContext = savedContext; + } + goto done; + } + /* + * Special note: it's possible that + * bindPtr->newItem == bindPtr->currentItem + * here. This can happen, for example, if LEFT_GRABBED_ITEM was set. + */ + + bindPtr->flags &= ~LEFT_GRABBED_ITEM; + bindPtr->currentItem = bindPtr->newItem = newItem; + bindPtr->currentContext = bindPtr->newContext = newContext; + if (bindPtr->currentItem != NULL) { + XEvent event; + + event = bindPtr->pickEvent; + event.type = EnterNotify; + event.xcrossing.detail = NotifyAncestor; + DoEvent(bindPtr, &event, newItem, newContext); +#ifdef notdef + if ((eventPtr->type == MotionNotify) || + (eventPtr->type == ButtonRelease)) { + fprintf(stderr, "pickcurrentitem: DoEvent Button buttondown=%d.\n", + buttonDown); + event.type = eventPtr->type; + event.xbutton.button = eventPtr->xbutton.button; + DoEvent(bindPtr, &event, newItem, newContext); + fprintf(stderr, "pickcurrentitem: done.\n"); + } +#endif + } + done: + Tcl_Release(newItem); + Tcl_Release(oldItem); +} + +/* + *--------------------------------------------------------------------------- + * + * BindProc -- + * + * This procedure is invoked by the Tk dispatcher to handle events + * associated with bindings on items. + * + * Results: + * None. + * + * Side effects: + * Depends on the command invoked as part of the binding + * (if there was any). + * + *--------------------------------------------------------------------------- + */ +static void +BindProc( + ClientData clientData, /* Pointer to widget structure. */ + XEvent *eventPtr) /* Pointer to X event that just happened. */ +{ + BindTable *bindPtr = clientData; + int mask; + + Tcl_Preserve(bindPtr->clientData); + /* + * This code below keeps track of the current modifier state in + * bindPtr->state. This information is used to defer repicks of the + * current item while buttons are down. + */ + switch (eventPtr->type) { + case ButtonPress: + case ButtonRelease: + mask = 0; + if ((eventPtr->xbutton.button >= Button1) && + (eventPtr->xbutton.button <= Button5)) { + mask = buttonMasks[eventPtr->xbutton.button]; + } + /* + * For button press events, repick the current item using the button + * state before the event, then process the event. For button release + * events, first process the event, then repick the current item using + * the button state *after* the event (the button has logically gone + * up before we change the current item). + */ + if (eventPtr->type == ButtonPress) { + + /* + * On a button press, first repick the current item using the + * button state before the event, the process the event. + */ + + bindPtr->state = eventPtr->xbutton.state; + PickCurrentItem(bindPtr, eventPtr); + bindPtr->state ^= mask; + DoEvent(bindPtr, eventPtr, bindPtr->currentItem, + bindPtr->currentContext); + + } else { + + /* + * Button release: first process the event, with the button still + * considered to be down. Then repick the current item under the + * assumption that the button is no longer down. + */ + bindPtr->state = eventPtr->xbutton.state; + DoEvent(bindPtr, eventPtr, bindPtr->currentItem, + bindPtr->currentContext); + eventPtr->xbutton.state ^= mask; + bindPtr->state = eventPtr->xbutton.state; + PickCurrentItem(bindPtr, eventPtr); + eventPtr->xbutton.state ^= mask; + } + break; + + case EnterNotify: + case LeaveNotify: + bindPtr->state = eventPtr->xcrossing.state; + PickCurrentItem(bindPtr, eventPtr); + break; + + case MotionNotify: + bindPtr->state = eventPtr->xmotion.state; + PickCurrentItem(bindPtr, eventPtr); + DoEvent(bindPtr, eventPtr, bindPtr->currentItem, + bindPtr->currentContext); + break; + + case KeyPress: + case KeyRelease: + bindPtr->state = eventPtr->xkey.state; + PickCurrentItem(bindPtr, eventPtr); + DoEvent(bindPtr, eventPtr, bindPtr->currentItem, + bindPtr->currentContext); + break; + } + Tcl_Release(bindPtr->clientData); +} + +int +Blt_ConfigureBindings( + Tcl_Interp *interp, + BindTable *bindPtr, + ClientData item, + int argc, + const char **argv) +{ + const char *command; + unsigned long mask; + const char *seq; + + if (argc == 0) { + Tk_GetAllBindings(interp, bindPtr->bindingTable, item); + return TCL_OK; + } + if (argc == 1) { + command = Tk_GetBinding(interp, bindPtr->bindingTable, item, argv[0]); + if (command == NULL) { + Tcl_AppendResult(interp, "can't find event \"", argv[0], "\"", + (char *)NULL); + return TCL_ERROR; + } + Tcl_SetStringObj(Tcl_GetObjResult(interp), command, -1); + return TCL_OK; + } + + seq = argv[0]; + command = argv[1]; + + if (command[0] == '\0') { + return Tk_DeleteBinding(interp, bindPtr->bindingTable, item, seq); + } + + if (command[0] == '+') { + mask = Tk_CreateBinding(interp, bindPtr->bindingTable, item, seq, + command + 1, TRUE); + } else { + mask = Tk_CreateBinding(interp, bindPtr->bindingTable, item, seq, + command, FALSE); + } + if (mask == 0) { + Tcl_AppendResult(interp, "event mask can't be zero for \"", item, "\"", + (char *)NULL); + return TCL_ERROR; + } + if (mask & (unsigned)~ALL_VALID_EVENTS_MASK) { + Tk_DeleteBinding(interp, bindPtr->bindingTable, item, seq); + Tcl_ResetResult(interp); + Tcl_AppendResult(interp, "requested illegal events; ", + "only key, button, motion, enter, leave, and virtual ", + "events may be used", (char *)NULL); + return TCL_ERROR; + } + return TCL_OK; +} + + +int +Blt_ConfigureBindingsFromObj( + Tcl_Interp *interp, + BindTable *bindPtr, + ClientData item, + int objc, + Tcl_Obj *const *objv) +{ + const char *command; + unsigned long mask; + const char *seq; + const char *string; + + if (objc == 0) { + Tk_GetAllBindings(interp, bindPtr->bindingTable, item); + return TCL_OK; + } + string = Tcl_GetString(objv[0]); + if (objc == 1) { + command = Tk_GetBinding(interp, bindPtr->bindingTable, item, string); + if (command == NULL) { + Tcl_ResetResult(interp); + Tcl_AppendResult(interp, "invalid binding event \"", string, "\"", + (char *)NULL); + return TCL_ERROR; + } + Tcl_SetStringObj(Tcl_GetObjResult(interp), command, -1); + return TCL_OK; + } + + seq = string; + command = Tcl_GetString(objv[1]); + + if (command[0] == '\0') { + return Tk_DeleteBinding(interp, bindPtr->bindingTable, item, seq); + } + + if (command[0] == '+') { + mask = Tk_CreateBinding(interp, bindPtr->bindingTable, item, seq, + command + 1, TRUE); + } else { + mask = Tk_CreateBinding(interp, bindPtr->bindingTable, item, seq, + command, FALSE); + } + if (mask == 0) { + return TCL_ERROR; + } + if (mask & (unsigned)~ALL_VALID_EVENTS_MASK) { + Tk_DeleteBinding(interp, bindPtr->bindingTable, item, seq); + Tcl_ResetResult(interp); + Tcl_AppendResult(interp, "requested illegal events; ", + "only key, button, motion, enter, leave, and virtual ", + "events may be used", (char *)NULL); + return TCL_ERROR; + } + return TCL_OK; +} + +Blt_BindTable +Blt_CreateBindingTable( + Tcl_Interp *interp, + Tk_Window tkwin, + ClientData clientData, + Blt_BindPickProc *pickProc, + Blt_BindTagProc *tagProc) +{ + unsigned int mask; + BindTable *bindPtr; + + bindPtr = Blt_AssertCalloc(1, sizeof(BindTable)); + bindPtr->bindingTable = Tk_CreateBindingTable(interp); + bindPtr->clientData = clientData; + bindPtr->tkwin = tkwin; + bindPtr->pickProc = pickProc; + bindPtr->tagProc = tagProc; + mask = (KeyPressMask | KeyReleaseMask | ButtonPressMask | + ButtonReleaseMask | EnterWindowMask | LeaveWindowMask | + PointerMotionMask); + Tk_CreateEventHandler(tkwin, mask, BindProc, bindPtr); + return bindPtr; +} + +void +Blt_DestroyBindingTable(BindTable *bindPtr) +{ + unsigned int mask; + + Tk_DeleteBindingTable(bindPtr->bindingTable); + mask = (KeyPressMask | KeyReleaseMask | ButtonPressMask | + ButtonReleaseMask | EnterWindowMask | LeaveWindowMask | + PointerMotionMask); + Tk_DeleteEventHandler(bindPtr->tkwin, mask, BindProc, bindPtr); + Blt_Free(bindPtr); +} + +void +Blt_PickCurrentItem(BindTable *bindPtr) +{ + if (bindPtr->activePick) { + PickCurrentItem(bindPtr, &bindPtr->pickEvent); + } +} + +void +Blt_DeleteBindings( + BindTable *bindPtr, + ClientData object) +{ + Tk_DeleteAllBindings(bindPtr->bindingTable, object); + + /* + * If this is the object currently picked, we need to repick one. + */ + if (bindPtr->currentItem == object) { + bindPtr->currentItem = NULL; + bindPtr->currentContext = NULL; + } + if (bindPtr->newItem == object) { + bindPtr->newItem = NULL; + bindPtr->newContext = NULL; + } + if (bindPtr->focusItem == object) { + bindPtr->focusItem = NULL; + bindPtr->focusContext = NULL; + } +} + +void +Blt_MoveBindingTable( + BindTable *bindPtr, + Tk_Window tkwin) +{ + unsigned int mask; + + mask = (KeyPressMask | KeyReleaseMask | ButtonPressMask | + ButtonReleaseMask | EnterWindowMask | LeaveWindowMask | + PointerMotionMask); + if (bindPtr->tkwin != NULL) { + Tk_DeleteEventHandler(bindPtr->tkwin, mask, BindProc, bindPtr); + } + Tk_CreateEventHandler(tkwin, mask, BindProc, bindPtr); + bindPtr->tkwin = tkwin; +} + +/* + * The following union is used to hold the detail information from an + * XEvent (including Tk's XVirtualEvent extension). + */ +typedef union { + KeySym keySym; /* KeySym that corresponds to xkey.keycode. */ + int button; /* Button that was pressed (xbutton.button). */ + Tk_Uid name; /* Tk_Uid of virtual event. */ + ClientData clientData; /* Used when type of Detail is unknown, and to + * ensure that all bytes of Detail are initialized + * when this structure is used in a hash key. */ +} Detail; + + +/* + * The following structure defines a pattern, which is matched against X + * events as part of the process of converting X events into TCL commands. + */ +typedef struct { + int eventType; /* Type of X event, e.g. ButtonPress. */ + int needMods; /* Mask of modifiers that must be + * present (0 means no modifiers are + * required). */ + Detail detail; /* Additional information that must + * match event. Normally this is 0, + * meaning no additional information + * must match. For KeyPress and + * KeyRelease events, a keySym may + * be specified to select a + * particular keystroke (0 means any + * keystrokes). For button events, + * specifies a particular button (0 + * means any buttons are OK). For virtual + * events, specifies the Tk_Uid of the + * virtual event name (never 0). */ +} Pattern; + +typedef struct { + const char *name; /* Name of modifier. */ + int mask; /* Button/modifier mask value, such as + * Button1Mask. */ + int flags; /* Various flags; see below for + * definitions. */ +} EventModifier; + +/* + * Flags for EventModifier structures: + * + * DOUBLE - Non-zero means duplicate this event, + * e.g. for double-clicks. + * TRIPLE - Non-zero means triplicate this event, + * e.g. for triple-clicks. + * QUADRUPLE - Non-zero means quadruple this event, + * e.g. for 4-fold-clicks. + * MULT_CLICKS - Combination of all of above. + */ + +#define DOUBLE (1<<0) +#define TRIPLE (1<<1) +#define QUADRUPLE (1<<2) +#define MULT_CLICKS (DOUBLE|TRIPLE|QUADRUPLE) + +#define META_MASK (AnyModifier<<1) +#define ALT_MASK (AnyModifier<<2) + +static EventModifier eventModifiers[] = { + {"Alt", ALT_MASK, 0}, + {"Any", 0, 0}, /* Ignored: historical relic. */ + {"B1", Button1Mask, 0}, + {"B2", Button2Mask, 0}, + {"B3", Button3Mask, 0}, + {"B4", Button4Mask, 0}, + {"B5", Button5Mask, 0}, + {"Button1", Button1Mask, 0}, + {"Button2", Button2Mask, 0}, + {"Button3", Button3Mask, 0}, + {"Button4", Button4Mask, 0}, + {"Button5", Button5Mask, 0}, + {"Command", Mod1Mask, 0}, + {"Control", ControlMask, 0}, + {"Double", 0, DOUBLE}, + {"Lock", LockMask, 0}, + {"M", META_MASK, 0}, + {"M1", Mod1Mask, 0}, + {"M2", Mod2Mask, 0}, + {"M3", Mod3Mask, 0}, + {"M4", Mod4Mask, 0}, + {"M5", Mod5Mask, 0}, + {"Meta", META_MASK, 0}, + {"Mod1", Mod1Mask, 0}, + {"Mod2", Mod2Mask, 0}, + {"Mod3", Mod3Mask, 0}, + {"Mod4", Mod4Mask, 0}, + {"Mod5", Mod5Mask, 0}, + {"Option", Mod2Mask, 0}, + {"Quadruple", 0, QUADRUPLE}, + {"Shift", ShiftMask, 0}, + {"Triple", 0, TRIPLE}, +}; + +typedef struct { + const char *name; /* Name of event. */ + int type; /* Event type for X, such as + * ButtonPress. */ + int eventMask; /* Mask bits (for XSelectInput) + * for this event type. */ +} EventInfo; + +/* + * Note: some of the masks below are an OR-ed combination of + * several masks. This is necessary because X doesn't report + * up events unless you also ask for down events. Also, X + * doesn't report button state in motion events unless you've + * asked about button events. + */ + +static EventInfo events[] = { + {"Activate", ActivateNotify, ActivateMask}, + {"Button", ButtonPress, ButtonPressMask}, + {"ButtonPress", ButtonPress, ButtonPressMask}, + {"ButtonRelease", ButtonRelease, ButtonPressMask|ButtonReleaseMask}, + {"Circulate", CirculateNotify, StructureNotifyMask}, + {"CirculateRequest", CirculateRequest, SubstructureRedirectMask}, + {"Colormap", ColormapNotify, ColormapChangeMask}, + {"Configure", ConfigureNotify, StructureNotifyMask}, + {"ConfigureRequest", ConfigureRequest, SubstructureRedirectMask}, + {"Create", CreateNotify, SubstructureNotifyMask}, + {"Deactivate", DeactivateNotify, ActivateMask}, + {"Destroy", DestroyNotify, StructureNotifyMask}, + {"Enter", EnterNotify, EnterWindowMask}, + {"Expose", Expose, ExposureMask}, + {"FocusIn", FocusIn, FocusChangeMask}, + {"FocusOut", FocusOut, FocusChangeMask}, + {"Gravity", GravityNotify, StructureNotifyMask}, + {"Key", KeyPress, KeyPressMask}, + {"KeyPress", KeyPress, KeyPressMask}, + {"KeyRelease", KeyRelease, KeyPressMask|KeyReleaseMask}, + {"Leave", LeaveNotify, LeaveWindowMask}, + {"Map", MapNotify, StructureNotifyMask}, + {"MapRequest", MapRequest, SubstructureRedirectMask}, + {"Motion", MotionNotify, ButtonPressMask|PointerMotionMask}, + {"MouseWheel", MouseWheelEvent, MouseWheelMask}, + {"Property", PropertyNotify, PropertyChangeMask}, + {"Reparent", ReparentNotify, StructureNotifyMask}, + {"ResizeRequest", ResizeRequest, ResizeRedirectMask}, + {"Unmap", UnmapNotify, StructureNotifyMask}, + {"Visibility", VisibilityNotify, VisibilityChangeMask}, +}; + +/* + * The defines and table below are used to classify events into + * various groups. The reason for this is that logically identical + * fields (e.g. "state") appear at different places in different + * types of events. The classification masks can be used to figure + * out quickly where to extract information from events. + */ + +#define KEY 0x1 +#define BUTTON 0x2 +#define MOTION 0x4 +#define CROSSING 0x8 +#define FOCUS 0x10 +#define EXPOSE 0x20 +#define VISIBILITY 0x40 +#define CREATE 0x80 +#define DESTROY 0x100 +#define UNMAP 0x200 +#define MAP 0x400 +#define REPARENT 0x800 +#define CONFIG 0x1000 +#define GRAVITY 0x2000 +#define CIRC 0x4000 +#define PROP 0x8000 +#define COLORMAP 0x10000 +#define VIRTUAL 0x20000 +#define ACTIVATE 0x40000 +#define MAPREQ 0x80000 +#define CONFIGREQ 0x100000 +#define RESIZEREQ 0x200000 +#define CIRCREQ 0x400000 + +#define KEY_BUTTON_MOTION_VIRTUAL (KEY|BUTTON|MOTION|VIRTUAL) +#define KEY_BUTTON_MOTION_CROSSING (KEY|BUTTON|MOTION|CROSSING|VIRTUAL) + +static int flagArray[TK_LASTEVENT+1] = { + /* Not used */ 0, + /* Not used */ 0, + /* KeyPress */ KEY, + /* KeyRelease */ KEY, + /* ButtonPress */ BUTTON, + /* ButtonRelease */ BUTTON, + /* MotionNotify */ MOTION, + /* EnterNotify */ CROSSING, + /* LeaveNotify */ CROSSING, + /* FocusIn */ FOCUS, + /* FocusOut */ FOCUS, + /* KeymapNotify */ 0, + /* Expose */ EXPOSE, + /* GraphicsExpose */ EXPOSE, + /* NoExpose */ 0, + /* VisibilityNotify */ VISIBILITY, + /* CreateNotify */ CREATE, + /* DestroyNotify */ DESTROY, + /* UnmapNotify */ UNMAP, + /* MapNotify */ MAP, + /* MapRequest */ MAPREQ, + /* ReparentNotify */ REPARENT, + /* ConfigureNotify */ CONFIG, + /* ConfigureRequest */ CONFIGREQ, + /* GravityNotify */ GRAVITY, + /* ResizeRequest */ RESIZEREQ, + /* CirculateNotify */ CIRC, + /* CirculateRequest */ 0, + /* PropertyNotify */ PROP, + /* SelectionClear */ 0, + /* SelectionRequest */ 0, + /* SelectionNotify */ 0, + /* ColormapNotify */ COLORMAP, + /* ClientMessage */ 0, + /* MappingNotify */ 0, +#ifdef GenericEvent + /* GenericEvent */ 0, +#endif + /* VirtualEvent */ VIRTUAL, + /* Activate */ ACTIVATE, + /* Deactivate */ ACTIVATE, + /* MouseWheel */ KEY +}; + + +static EventModifier * +FindModifier(const char *string) +{ + int high, low; + char c; + + low = 0; + high = (sizeof(eventModifiers) / sizeof(EventModifier)) - 1; + c = string[0]; + while (low <= high) { + EventModifier *modPtr; + int compare; + int median; + + median = (low + high) >> 1; + modPtr = eventModifiers + median; + + /* Test the first character */ + compare = c - modPtr->name[0]; + if (compare == 0) { + compare = strcmp(string, modPtr->name); + } + if (compare < 0) { + high = median - 1; + } else if (compare > 0) { + low = median + 1; + } else { + return modPtr; /* Modifier found. */ + } + } + return NULL; /* Can't find modifier */ +} + +static EventInfo * +FindEvent(const char *string) +{ + int high, low; + char c; + + low = 0; + high = (sizeof(events) / sizeof(EventInfo)) - 1; + c = string[0]; + while (low <= high) { + EventInfo *infoPtr; + int compare; + int median; + + median = (low + high) >> 1; + infoPtr = events + median; + + /* Test the first character */ + compare = c - infoPtr->name[0]; + if (compare == 0) { + compare = strcmp(string, infoPtr->name); + } + if (compare < 0) { + high = median - 1; + } else if (compare > 0) { + low = median + 1; + } else { + return infoPtr; /* Event found. */ + } + } + return NULL; /* Can't find event. */ +} + + +/* + *---------------------------------------------------------------------- + * + * GetField -- + * + * Used to parse pattern descriptions. Copies up to + * size characters from p to copy, stopping at end of + * string, space, "-", ">", or whenever size is + * exceeded. + * + * Results: + * The return value is a pointer to the character just + * after the last one copied (usually "-" or space or + * ">", but could be anything if size was exceeded). + * Also places NULL-terminated string (up to size + * character, including NULL), at copy. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static char * +GetField(p, copy, size) + char *p; /* Pointer to part of pattern. */ + char *copy; /* Place to copy field. */ + int size; /* Maximum number of characters to + * copy. */ +{ + while ((*p != '\0') && !isspace(UCHAR(*p)) && (*p != '>') + && (*p != '-') && (size > 1)) { + *copy = *p; + p++; + copy++; + size--; + } + *copy = '\0'; + return p; +} + +static int +ParseEventDescription(Tcl_Interp *interp, const char **eventStringPtr, + Pattern *patPtr, unsigned long *eventMaskPtr) +{ + char *p; + unsigned long eventMask; + int count, eventFlags; +#define FIELD_SIZE 48 + char field[FIELD_SIZE]; + EventInfo *infoPtr; + + Tcl_DString copy; + Tcl_DStringInit(©); + p = Tcl_DStringAppend(©, *eventStringPtr, -1); + + patPtr->eventType = -1; + patPtr->needMods = 0; + patPtr->detail.clientData = 0; + + eventMask = 0; + count = 1; + + /* + * Handle simple ASCII characters. + */ + + if (*p != '<') { + char string[2]; + + patPtr->eventType = KeyPress; + eventMask = KeyPressMask; + string[0] = *p; + string[1] = 0; + patPtr->detail.keySym = XStringToKeysym(string); + if (patPtr->detail.keySym == NoSymbol) { + if (isprint(UCHAR(*p))) { + patPtr->detail.keySym = *p; + } else { + char buf[64]; + + sprintf(buf, "bad ASCII character 0x%x", (unsigned char) *p); + Tcl_SetResult(interp, buf, TCL_VOLATILE); + count = 0; + goto done; + } + } + p++; + goto end; + } + + /* + * A physical event description consists of: + * + * 1. open angle bracket. + * 2. any number of modifiers, each followed by spaces + * or dashes. + * 3. an optional event name. + * 4. an option button or keysym name. Either this or + * item 3 *must* be present; if both are present + * then they are separated by spaces or dashes. + * 5. a close angle bracket. + */ + + p++; + + while (1) { + EventModifier *modPtr; + p = GetField(p, field, FIELD_SIZE); + if (*p == '>') { + /* + * This solves the problem of, e.g., <Control-M> being + * misinterpreted as Control + Meta + missing keysym + * instead of Control + KeyPress + M. + */ + break; + } + modPtr = FindModifier(field); + if (modPtr == NULL) { + break; + } + patPtr->needMods |= modPtr->mask; + if (modPtr->flags & (MULT_CLICKS)) { + int i = modPtr->flags & MULT_CLICKS; + count = 2; + while (i >>= 1) count++; + } + while ((*p == '-') || isspace(UCHAR(*p))) { + p++; + } + } + + eventFlags = 0; + infoPtr = FindEvent(field); + if (infoPtr != NULL) { + patPtr->eventType = infoPtr->type; + eventFlags = flagArray[infoPtr->type]; + eventMask = infoPtr->eventMask; + while ((*p == '-') || isspace(UCHAR(*p))) { + p++; + } + p = GetField(p, field, FIELD_SIZE); + } + if (*field != '\0') { + if ((*field >= '1') && (*field <= '5') && (field[1] == '\0')) { + if (eventFlags == 0) { + patPtr->eventType = ButtonPress; + eventMask = ButtonPressMask; + } else if (eventFlags & KEY) { + goto getKeysym; + } else if ((eventFlags & BUTTON) == 0) { + Tcl_AppendResult(interp, "specified button \"", field, + "\" for non-button event", (char *) NULL); + count = 0; + goto done; + } + patPtr->detail.button = (*field - '0'); + } else { + getKeysym: + patPtr->detail.keySym = XStringToKeysym(field); + if (patPtr->detail.keySym == NoSymbol) { + Tcl_AppendResult(interp, "bad event type or keysym \"", + field, "\"", (char *)NULL); + count = 0; + goto done; + } + if (eventFlags == 0) { + patPtr->eventType = KeyPress; + eventMask = KeyPressMask; + } else if ((eventFlags & KEY) == 0) { + Tcl_AppendResult(interp, "specified keysym \"", field, + "\" for non-key event", (char *)NULL); + count = 0; + goto done; + } + } + } else if (eventFlags == 0) { + Tcl_AppendResult(interp, "no event type or button # or keysym", + (char *)NULL); + count = 0; + goto done; + } + + while ((*p == '-') || isspace(UCHAR(*p))) { + p++; + } + if (*p != '>') { + while (*p != '\0') { + p++; + if (*p == '>') { + Tcl_AppendResult(interp, + "extra characters after detail in binding", + (char *)NULL); + count = 0; + goto done; + } + } + Tcl_AppendResult(interp, "missing \">\" in binding", (char *)NULL); + count = 0; + goto done; + } + p++; + +end: + *eventStringPtr += (p - Tcl_DStringValue(©)); + *eventMaskPtr |= eventMask; +done: + Tcl_DStringFree(©); + return count; +} + +typedef struct { + int numKey; /* Integer representation of a value. */ + const char *strKey; /* String representation of a value. */ +} TkStateMap; + +static TkStateMap notifyMode[] = { + {NotifyNormal, "NotifyNormal"}, + {NotifyGrab, "NotifyGrab"}, + {NotifyUngrab, "NotifyUngrab"}, + {NotifyWhileGrabbed, "NotifyWhileGrabbed"}, + {-1, NULL} +}; + +static TkStateMap notifyDetail[] = { + {NotifyAncestor, "NotifyAncestor"}, + {NotifyVirtual, "NotifyVirtual"}, + {NotifyInferior, "NotifyInferior"}, + {NotifyNonlinear, "NotifyNonlinear"}, + {NotifyNonlinearVirtual, "NotifyNonlinearVirtual"}, + {NotifyPointer, "NotifyPointer"}, + {NotifyPointerRoot, "NotifyPointerRoot"}, + {NotifyDetailNone, "NotifyDetailNone"}, + {-1, NULL} +}; + +static TkStateMap circPlace[] = { + {PlaceOnTop, "PlaceOnTop"}, + {PlaceOnBottom, "PlaceOnBottom"}, + {-1, NULL} +}; + +static TkStateMap visNotify[] = { + {VisibilityUnobscured, "VisibilityUnobscured"}, + {VisibilityPartiallyObscured, "VisibilityPartiallyObscured"}, + {VisibilityFullyObscured, "VisibilityFullyObscured"}, + {-1, NULL} +}; + +static TkStateMap configureRequestDetail[] = { + {None, "None"}, + {Above, "Above"}, + {Below, "Below"}, + {BottomIf, "BottomIf"}, + {TopIf, "TopIf"}, + {Opposite, "Opposite"}, + {-1, NULL} +}; + +static TkStateMap propNotify[] = { + {PropertyNewValue, "NewValue"}, + {PropertyDelete, "Delete"}, + {-1, NULL} +}; + +/* + *--------------------------------------------------------------------------- + * + * HandleEventGenerate -- + * + * Helper function for the "event generate" command. Generate and + * process an XEvent, constructed from information parsed from the + * event description string and its optional arguments. + * + * argv[0] contains name of the target window. + * argv[1] contains pattern string for one event (e.g, <Control-v>). + * argv[2..argc-1] contains -field/option pairs for specifying + * additional detail in the generated event. + * + * Either virtual or physical events can be generated this way. + * The event description string must contain the specification + * for only one event. + * + * Results: + * None. + * + * Side effects: + * When constructing the event, + * event.xany.serial is filled with the current X serial number. + * event.xany.window is filled with the target window. + * event.xany.display is filled with the target window's display. + * Any other fields in eventPtr which are not specified by the pattern + * string or the optional arguments, are set to 0. + * + * The event may be handled sychronously or asynchronously, depending + * on the value specified by the optional "-when" option. The + * default setting is synchronous. + * + *--------------------------------------------------------------------------- + */ +static int +SendEventCmd(ClientData clientData, Tcl_Interp *interp, int objc, + Tcl_Obj *const *objv) +{ + XEvent event; + CONST char *p; + char *name; + Window window; + Display *display; + Tk_Window tkwin; + int count, flags, synch, i, number, warp; + Tcl_QueuePosition pos; + Pattern pat; + unsigned long eventMask; + static CONST char *fieldStrings[] = { + "-when", "-above", "-borderwidth", "-button", + "-count", "-delta", "-detail", "-focus", + "-height", + "-keycode", "-keysym", "-mode", "-override", + "-place", "-root", "-rootx", "-rooty", + "-sendevent", "-serial", "-state", "-subwindow", + "-time", "-warp", "-width", "-window", + "-x", "-y", NULL + }; + enum field { + EVENT_WHEN, EVENT_ABOVE, EVENT_BORDER, EVENT_BUTTON, + EVENT_COUNT, EVENT_DELTA, EVENT_DETAIL, EVENT_FOCUS, + EVENT_HEIGHT, + EVENT_KEYCODE, EVENT_KEYSYM, EVENT_MODE, EVENT_OVERRIDE, + EVENT_PLACE, EVENT_ROOT, EVENT_ROOTX, EVENT_ROOTY, + EVENT_SEND, EVENT_SERIAL, EVENT_STATE, EVENT_SUBWINDOW, + EVENT_TIME, EVENT_WARP, EVENT_WIDTH, EVENT_WINDOW, + EVENT_X, EVENT_Y + }; + tkwin = Tk_MainWindow(interp); + if (Blt_GetWindowFromObj(interp, objv[1], &window) != TCL_OK) { + return TCL_ERROR; + } + name = Tcl_GetStringFromObj(objv[2], NULL); + + display = Tk_Display(tkwin); + p = name; + eventMask = 0; + count = ParseEventDescription(interp, &p, &pat, &eventMask); + if (count == 0) { + return TCL_ERROR; + } + if (count != 1) { + Tcl_AppendResult(interp, "Double or Triple modifier not allowed", + (char *)NULL); + return TCL_ERROR; + } + if (*p != '\0') { + Tcl_AppendResult(interp, "only one event specification allowed", + (char *)NULL); + return TCL_ERROR; + } + + memset((VOID *) &event, 0, sizeof(event)); + event.xany.type = pat.eventType; + event.xany.serial = NextRequest(display); + event.xany.send_event = False; + event.xany.window = window; + event.xany.display = display; + + flags = flagArray[event.xany.type]; + if (flags & DESTROY) { + /* + * Event DestroyNotify should be generated by destroying + * the window. + */ + XDestroyWindow(display, window); + return TCL_OK; + } + if (flags & (KEY_BUTTON_MOTION_VIRTUAL)) { + event.xkey.state = pat.needMods; + if ((flags & KEY) && (event.xany.type != MouseWheelEvent)) { + TkpSetKeycodeAndState(tkwin, pat.detail.keySym, &event); + } else if (flags & BUTTON) { + event.xbutton.button = pat.detail.button; + } else if (flags & VIRTUAL) { + ((XVirtualEvent *) &event)->name = pat.detail.name; + } + } + if (flags & (CREATE|UNMAP|MAP|REPARENT|CONFIG|GRAVITY|CIRC)) { + event.xcreatewindow.window = event.xany.window; + } + + if (flags & (KEY_BUTTON_MOTION_VIRTUAL|CROSSING)) { + event.xkey.x_root = -1; + event.xkey.y_root = -1; + } + + /* + * Process the remaining arguments to fill in additional fields + * of the event. + */ + + synch = 1; + warp = 0; + pos = TCL_QUEUE_TAIL; + for (i = 3; i < objc; i += 2) { + Tcl_Obj *optionPtr, *valuePtr; + int index; + + optionPtr = objv[i]; + valuePtr = objv[i + 1]; + + if (Tcl_GetIndexFromObj(interp, optionPtr, fieldStrings, "option", + TCL_EXACT, &index) != TCL_OK) { + return TCL_ERROR; + } + if ((objc & 1) == 0) { + /* + * This test occurs after Tcl_GetIndexFromObj() so that + * "event generate <Button> -xyz" will return the error message + * that "-xyz" is a bad option, rather than that the value + * for "-xyz" is missing. + */ + + Tcl_AppendResult(interp, "value for \"", + Tcl_GetStringFromObj(optionPtr, NULL), "\" missing", + (char *)NULL); + return TCL_ERROR; + } + + switch ((enum field) index) { + case EVENT_WARP: { + if (Tcl_GetBooleanFromObj(interp, valuePtr, &warp) != TCL_OK) { + return TCL_ERROR; + } + if (!(flags & (KEY_BUTTON_MOTION_VIRTUAL))) { + goto badopt; + } + break; + } + case EVENT_WHEN: { +#ifdef notdef + pos = (Tcl_QueuePosition) TkFindStateNumObj(interp, optionPtr, + queuePosition, valuePtr); + if ((int) pos < -1) { + return TCL_ERROR; + } + synch = 0; + if ((int) pos == -1) { + synch = 1; + } +#endif + break; + } + case EVENT_ABOVE: { + Window window2; + + if (Blt_GetWindowFromObj(interp, valuePtr, &window2) != TCL_OK) { + return TCL_ERROR; + } + if (flags & CONFIG) { + event.xconfigure.above = window2; + } else { + goto badopt; + } + break; + } + case EVENT_BORDER: { + if (Tk_GetPixelsFromObj(interp, tkwin, valuePtr, &number)!=TCL_OK) { + return TCL_ERROR; + } + if (flags & (CREATE|CONFIG)) { + event.xcreatewindow.border_width = number; + } else { + goto badopt; + } + break; + } + case EVENT_BUTTON: { + if (Tcl_GetIntFromObj(interp, valuePtr, &number) != TCL_OK) { + return TCL_ERROR; + } + if (flags & BUTTON) { + event.xbutton.button = number; + } else { + goto badopt; + } + break; + } + case EVENT_COUNT: { + if (Tcl_GetIntFromObj(interp, valuePtr, &number) != TCL_OK) { + return TCL_ERROR; + } + if (flags & EXPOSE) { + event.xexpose.count = number; + } else { + goto badopt; + } + break; + } + case EVENT_DELTA: { + if (Tcl_GetIntFromObj(interp, valuePtr, &number) != TCL_OK) { + return TCL_ERROR; + } + if ((flags & KEY) && (event.xkey.type == MouseWheelEvent)) { + event.xkey.keycode = number; + } else { + goto badopt; + } + break; + } + case EVENT_DETAIL: { + number = TkFindStateNumObj(interp, optionPtr, notifyDetail, + valuePtr); + if (number < 0) { + return TCL_ERROR; + } + if (flags & FOCUS) { + event.xfocus.detail = number; + } else if (flags & CROSSING) { + event.xcrossing.detail = number; + } else { + goto badopt; + } + break; + } + case EVENT_FOCUS: { + if (Tcl_GetBooleanFromObj(interp, valuePtr, &number) != TCL_OK) { + return TCL_ERROR; + } + if (flags & CROSSING) { + event.xcrossing.focus = number; + } else { + goto badopt; + } + break; + } + case EVENT_HEIGHT: { + if (Tk_GetPixelsFromObj(interp, tkwin, valuePtr, &number) != TCL_OK) { + return TCL_ERROR; + } + if (flags & EXPOSE) { + event.xexpose.height = number; + } else if (flags & CONFIG) { + event.xconfigure.height = number; + } else { + goto badopt; + } + break; + } + case EVENT_KEYCODE: { + if (Tcl_GetIntFromObj(interp, valuePtr, &number) != TCL_OK) { + return TCL_ERROR; + } + if ((flags & KEY) && (event.xkey.type != MouseWheelEvent)) { + event.xkey.keycode = number; + } else { + goto badopt; + } + break; + } + case EVENT_KEYSYM: { + KeySym keysym; + char *value; + + value = Tcl_GetStringFromObj(valuePtr, NULL); + keysym = TkStringToKeysym(value); + if (keysym == NoSymbol) { + Tcl_AppendResult(interp, "unknown keysym \"", value, "\"", + (char *)NULL); + return TCL_ERROR; + } + + TkpSetKeycodeAndState(tkwin, keysym, &event); + if (event.xkey.keycode == 0) { + Tcl_AppendResult(interp, "no keycode for keysym \"", value, + "\"", (char *)NULL); + return TCL_ERROR; + } + if (!(flags & KEY) || (event.xkey.type == MouseWheelEvent)) { + goto badopt; + } + break; + } + case EVENT_MODE: { + number = TkFindStateNumObj(interp, optionPtr, notifyMode, + valuePtr); + if (number < 0) { + return TCL_ERROR; + } + if (flags & CROSSING) { + event.xcrossing.mode = number; + } else if (flags & FOCUS) { + event.xfocus.mode = number; + } else { + goto badopt; + } + break; + } + case EVENT_OVERRIDE: { + if (Tcl_GetBooleanFromObj(interp, valuePtr, &number) != TCL_OK) { + return TCL_ERROR; + } + if (flags & CREATE) { + event.xcreatewindow.override_redirect = number; + } else if (flags & MAP) { + event.xmap.override_redirect = number; + } else if (flags & REPARENT) { + event.xreparent.override_redirect = number; + } else if (flags & CONFIG) { + event.xconfigure.override_redirect = number; + } else { + goto badopt; + } + break; + } + case EVENT_PLACE: { + number = TkFindStateNumObj(interp, optionPtr, circPlace, + valuePtr); + if (number < 0) { + return TCL_ERROR; + } + if (flags & CIRC) { + event.xcirculate.place = number; + } else { + goto badopt; + } + break; + } + case EVENT_ROOT: { + Window window2; + + if (Blt_GetWindowFromObj(interp, valuePtr, &window2) != TCL_OK) { + return TCL_ERROR; + } + if (flags & (KEY_BUTTON_MOTION_VIRTUAL|CROSSING)) { + event.xkey.root = window2; + } else { + goto badopt; + } + break; + } + case EVENT_ROOTX: { + if (Tk_GetPixelsFromObj(interp, tkwin, valuePtr, &number) != TCL_OK) { + return TCL_ERROR; + } + if (flags & (KEY_BUTTON_MOTION_VIRTUAL|CROSSING)) { + event.xkey.x_root = number; + } else { + goto badopt; + } + break; + } + case EVENT_ROOTY: { + if (Tk_GetPixelsFromObj(interp, tkwin, valuePtr, &number) != TCL_OK) { + return TCL_ERROR; + } + if (flags & (KEY_BUTTON_MOTION_VIRTUAL|CROSSING)) { + event.xkey.y_root = number; + } else { + goto badopt; + } + break; + } + case EVENT_SEND: { + CONST char *value; + + value = Tcl_GetStringFromObj(valuePtr, NULL); + if (isdigit(UCHAR(value[0]))) { + /* + * Allow arbitrary integer values for the field; they + * are needed by a few of the tests in the Tk test suite. + */ + + if (Tcl_GetIntFromObj(interp, valuePtr, &number) + != TCL_OK) { + return TCL_ERROR; + } + } else { + if (Tcl_GetBooleanFromObj(interp, valuePtr, &number) + != TCL_OK) { + return TCL_ERROR; + } + } + event.xany.send_event = number; + break; + } + case EVENT_SERIAL: { + if (Tcl_GetIntFromObj(interp, valuePtr, &number) != TCL_OK) { + return TCL_ERROR; + } + event.xany.serial = number; + break; + } + case EVENT_STATE: { + if (flags & (KEY_BUTTON_MOTION_VIRTUAL|CROSSING)) { + if (Tcl_GetIntFromObj(interp, valuePtr, &number) + != TCL_OK) { + return TCL_ERROR; + } + if (flags & (KEY_BUTTON_MOTION_VIRTUAL)) { + event.xkey.state = number; + } else { + event.xcrossing.state = number; + } + } else if (flags & VISIBILITY) { + number = TkFindStateNumObj(interp, optionPtr, visNotify, + valuePtr); + if (number < 0) { + return TCL_ERROR; + } + event.xvisibility.state = number; + } else { + goto badopt; + } + break; + } + case EVENT_SUBWINDOW: { + Window window2; + + if (Blt_GetWindowFromObj(interp, valuePtr, &window2) != TCL_OK) { + return TCL_ERROR; + } + if (flags & (KEY_BUTTON_MOTION_VIRTUAL|CROSSING)) { + event.xkey.subwindow = window2; + } else { + goto badopt; + } + break; + } + case EVENT_TIME: { + if (Tcl_GetIntFromObj(interp, valuePtr, &number) != TCL_OK) { + return TCL_ERROR; + } + if (flags & (KEY_BUTTON_MOTION_VIRTUAL|CROSSING)) { + event.xkey.time = (Time) number; + } else if (flags & PROP) { + event.xproperty.time = (Time) number; + } else { + goto badopt; + } + break; + } + case EVENT_WIDTH: { + if (Tk_GetPixelsFromObj(interp, tkwin, valuePtr, &number) + != TCL_OK) { + return TCL_ERROR; + } + if (flags & EXPOSE) { + event.xexpose.width = number; + } else if (flags & (CREATE|CONFIG)) { + event.xcreatewindow.width = number; + } else { + goto badopt; + } + break; + } + case EVENT_WINDOW: { + Window window2; + + if (Blt_GetWindowFromObj(interp, valuePtr, &window2) != TCL_OK) { + return TCL_ERROR; + } + if (flags & (CREATE|UNMAP|MAP|REPARENT|CONFIG + |GRAVITY|CIRC)) { + event.xcreatewindow.window = window2; + } else { + goto badopt; + } + break; + } + case EVENT_X: { + if (Tk_GetPixelsFromObj(interp, tkwin, valuePtr, &number) + != TCL_OK) { + return TCL_ERROR; + } + if (flags & (KEY_BUTTON_MOTION_VIRTUAL|CROSSING)) { + event.xkey.x = number; + /* + * Only modify rootx as well if it hasn't been changed. + */ + if (event.xkey.x_root == -1) { + int rootX, rootY; + + Tk_GetRootCoords(tkwin, &rootX, &rootY); + event.xkey.x_root = rootX + number; + } + } else if (flags & EXPOSE) { + event.xexpose.x = number; + } else if (flags & (CREATE|CONFIG|GRAVITY)) { + event.xcreatewindow.x = number; + } else if (flags & REPARENT) { + event.xreparent.x = number; + } else { + goto badopt; + } + break; + } + case EVENT_Y: { + if (Tk_GetPixelsFromObj(interp, tkwin, valuePtr, &number) + != TCL_OK) { + return TCL_ERROR; + } + if (flags & (KEY_BUTTON_MOTION_VIRTUAL|CROSSING)) { + event.xkey.y = number; + /* + * Only modify rooty as well if it hasn't been changed. + */ + if (event.xkey.y_root == -1) { + int rootX, rootY; + + Tk_GetRootCoords(tkwin, &rootX, &rootY); + event.xkey.y_root = rootY + number; + } + } else if (flags & EXPOSE) { + event.xexpose.y = number; + } else if (flags & (CREATE|CONFIG|GRAVITY)) { + event.xcreatewindow.y = number; + } else if (flags & REPARENT) { + event.xreparent.y = number; + } else { + goto badopt; + } + break; + } + } + continue; + + badopt: + Tcl_AppendResult(interp, name, " event doesn't accept \"", + Tcl_GetStringFromObj(optionPtr, NULL), "\" option", + (char *)NULL); + return TCL_ERROR; + } + if (!XSendEvent(display, window, False, pat.eventType, &event)) { + fprintf(stderr, "synthethic event failed\n"); + } + return TCL_OK; +} + +int +Blt_SendEventCmdInitProc(Tcl_Interp *interp) +{ + static Blt_InitCmdSpec cmdSpec = { + "sendevent", SendEventCmd, + }; + return Blt_InitCmd(interp, "::blt", &cmdSpec); +} diff --git a/src/bltGrBind.h b/src/bltGrBind.h new file mode 100644 index 0000000..2e2c5cb --- /dev/null +++ b/src/bltGrBind.h @@ -0,0 +1,120 @@ +/* + * bltBind.h -- + * + * Copyright 1998-2004 George A Howlett. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef _BLT_BIND_H +#define _BLT_BIND_H + +#include <bltList.h> + +typedef struct _Blt_BindTable *Blt_BindTable; + +typedef ClientData (Blt_BindPickProc)(ClientData clientData, int x, int y, + ClientData *contextPtr); + +typedef void (Blt_BindTagProc)(Blt_BindTable bindTable, ClientData object, + ClientData context, Blt_List list); + + +/* + * Binding structure information: + */ + +struct _Blt_BindTable { + unsigned int flags; + Tk_BindingTable bindingTable; + /* Table of all bindings currently defined. + * NULL means that no bindings exist, so the + * table hasn't been created. Each "object" + * used for this table is either a Tk_Uid for + * a tag or the address of an item named by + * id. */ + + ClientData currentItem; /* The item currently containing the mouse + * pointer, or NULL if none. */ + ClientData currentContext; /* One word indicating what kind of object was + * picked. */ + + ClientData newItem; /* The item that is about to become the + * current one, or NULL. This field is used + * to detect deletions of the new current item + * pointer that occur during Leave processing + * of the previous current tab. */ + ClientData newContext; /* One-word indicating what kind of object was + * just picked. */ + + ClientData focusItem; + ClientData focusContext; + + XEvent pickEvent; /* The event upon which the current choice of + * the current tab is based. Must be saved so + * that if the current item is deleted, we can + * pick another. */ + int activePick; /* The pick event has been initialized so that + * we can repick it */ + + int state; /* Last known modifier state. Used to defer + * picking a new current object while buttons + * are down. */ + + ClientData clientData; + Tk_Window tkwin; + Blt_BindPickProc *pickProc; /* Routine to report the item the mouse is + * currently over. */ + Blt_BindTagProc *tagProc; /* Routine to report tags picked items. */ +}; + +BLT_EXTERN void Blt_DestroyBindingTable(Blt_BindTable table); + +BLT_EXTERN Blt_BindTable Blt_CreateBindingTable(Tcl_Interp *interp, + Tk_Window tkwin, ClientData clientData, Blt_BindPickProc *pickProc, + Blt_BindTagProc *tagProc); + +BLT_EXTERN int Blt_ConfigureBindings(Tcl_Interp *interp, Blt_BindTable table, + ClientData item, int argc, const char **argv); + +BLT_EXTERN int Blt_ConfigureBindingsFromObj(Tcl_Interp *interp, + Blt_BindTable table, ClientData item, int objc, Tcl_Obj *const *objv); + +BLT_EXTERN void Blt_PickCurrentItem(Blt_BindTable table); + +BLT_EXTERN void Blt_DeleteBindings(Blt_BindTable table, ClientData object); + +BLT_EXTERN void Blt_MoveBindingTable(Blt_BindTable table, Tk_Window tkwin); + +#define Blt_SetFocusItem(bindPtr, object, context) \ + ((bindPtr)->focusItem = (ClientData)(object),\ + (bindPtr)->focusContext = (ClientData)(context)) + +#define Blt_SetCurrentItem(bindPtr, object, context) \ + ((bindPtr)->currentItem = (ClientData)(object),\ + (bindPtr)->currentContext = (ClientData)(context)) + +#define Blt_GetCurrentItem(bindPtr) ((bindPtr)->currentItem) +#define Blt_GetCurrentContext(bindPtr) ((bindPtr)->currentContext) +#define Blt_GetLatestItem(bindPtr) ((bindPtr)->newItem) + +#define Blt_GetBindingData(bindPtr) ((bindPtr)->clientData) + +#endif /*_BLT_BIND_H*/ diff --git a/src/bltGrElemBar.C b/src/bltGrElemBar.C new file mode 100644 index 0000000..328275a --- /dev/null +++ b/src/bltGrElemBar.C @@ -0,0 +1,2543 @@ + +/* + * bltGrBar.c -- + * + * This module implements barchart elements for the BLT graph widget. + * + * Copyright 1993-2004 George A Howlett. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include "bltGraph.h" +#include <X11/Xutil.h> + +#include "bltGrElem.h" + +#define CLAMP(x,l,h) ((x) = (((x)<(l))? (l) : ((x)>(h)) ? (h) : (x))) + +typedef struct { + float x1, y1, x2, y2; +} BarRegion; + +typedef struct { + Point2f ul, lr; + Segment2d segments[4]; + int nSegments; +} Bar; + +typedef struct { + const char *name; /* Pen style identifier. If NULL, pen + * was statically allocated. */ + ClassId classId; /* Type of pen */ + const char *typeId; /* String token identifying the type of + * pen */ + unsigned int flags; /* Indicates if the pen element is + * active or normal */ + int refCount; /* Reference count for elements using + * this pen. */ + Blt_HashEntry *hashPtr; + Blt_ConfigSpec *configSpecs; /* Configuration specifications */ + PenConfigureProc *configProc; + PenDestroyProc *destroyProc; + Graph *graphPtr; /* Graph that the pen is associated + * with. */ + /* Barchart specific pen fields start here. */ + XColor *outlineColor; /* Outline (foreground) color of bar */ + Blt_Background fill; /* 3D border and fill (background) + * color */ + int borderWidth; /* 3D border width of bar */ + int relief; /* Relief of the bar */ + Pixmap stipple; /* Stipple */ + GC fillGC; /* Graphics context */ + GC outlineGC; /* GC for outline of bar. */ + + /* Error bar attributes. */ + int errorBarShow; /* Describes which error bars to + * display: none, x, y, or * both. */ + + int errorBarLineWidth; /* Width of the error bar segments. */ + + int errorBarCapWidth; + XColor *errorBarColor; /* Color of the error bar. */ + + GC errorBarGC; /* Error bar graphics context. */ + + /* Show value attributes. */ + int valueShow; /* Indicates whether to display data + * value. Values are x, y, or none. */ + + const char *valueFormat; /* A printf format string. */ + TextStyle valueStyle; /* Text attributes (color, font, + * rotation, etc.) of the value. */ + +} BarPen; + +typedef struct { + Weight weight; /* Weight range where this pen is + * valid. */ + BarPen *penPtr; /* Pen to use. */ + + XRectangle *bars; /* Indicates starting location in bar + * array for this pen. */ + int nBars; /* # of bar segments for this pen. */ + + GraphSegments xeb, yeb; /* X and Y error bars. */ + + int symbolSize; /* Size of the pen's symbol scaled to + * the current graph size. */ + int errorBarCapWidth; /* Length of the cap ends on each error + * bar. */ + +} BarStyle; + +typedef struct { + GraphObj obj; /* Must be first field in element. */ + unsigned int flags; + Blt_HashEntry *hashPtr; + + /* Fields specific to elements. */ + const char *label; /* Label displayed in legend */ + unsigned short row, col; /* Position of the entry in the + * legend. */ + int legendRelief; /* Relief of label in legend. */ + Axis2d axes; /* X-axis and Y-axis mapping the + * element */ + ElemValues x, y, w; /* Contains array of floating point + * graph coordinate values. Also holds + * min/max and the number of + * coordinates */ + int *activeIndices; /* Array of indices (malloc-ed) which + * indicate which data points are active + * (drawn * with "active" colors). */ + int nActiveIndices; /* Number of active data points. + * Special case: if nActiveIndices < 0 + * and the active bit is set in "flags", + * then all data * points are drawn + * active. */ + ElementProcs *procsPtr; + Blt_ConfigSpec *configSpecs; /* Configuration specifications. */ + BarPen *activePenPtr; /* Standard Pens */ + BarPen *normalPenPtr; + BarPen *builtinPenPtr; + Blt_Chain stylePalette; /* Palette of pens. */ + + /* Symbol scaling */ + int scaleSymbols; /* If non-zero, the symbols will scale + * in size as the graph is zoomed + * in/out. */ + double xRange, yRange; /* Initial X-axis and Y-axis ranges: + * used to scale the size of element's + * symbol. */ + int state; + Blt_ChainLink link; + + /* Fields specific to the barchart element */ + + float barWidth; + const char *groupName; + + int *barToData; + XRectangle *bars; /* Array of rectangles comprising the bar + * segments of the element. */ + int *activeToData; + XRectangle *activeRects; + + int nBars; /* # of visible bar segments for + * element */ + int nActive; + + int xPad; /* Spacing on either side of bar */ + + ElemValues xError; /* Relative/symmetric X error values. */ + ElemValues yError; /* Relative/symmetric Y error values. */ + ElemValues xHigh, xLow; /* Absolute/asymmetric X-coordinate + * high/low error values. */ + ElemValues yHigh, yLow; /* Absolute/asymmetric Y-coordinate + * high/low error values. */ + BarPen builtinPen; + + GraphSegments xeb, yeb; + + int errorBarCapWidth; /* Length of cap on error bars */ +} BarElement; + +BLT_EXTERN Blt_CustomOption bltBarPenOption; +BLT_EXTERN Blt_CustomOption bltValuesOption; +BLT_EXTERN Blt_CustomOption bltValuePairsOption; +BLT_EXTERN Blt_CustomOption bltXAxisOption; +BLT_EXTERN Blt_CustomOption bltYAxisOption; +BLT_EXTERN Blt_CustomOption bltColorOption; +BLT_EXTERN Blt_CustomOption bltBarStylesOption; + +static Blt_OptionParseProc ObjToBarMode; +static Blt_OptionPrintProc BarModeToObj; +Blt_CustomOption bltBarModeOption = +{ + ObjToBarMode, BarModeToObj, NULL, (ClientData)0 +}; + +#define DEF_BAR_ACTIVE_PEN "activeBar" +#define DEF_BAR_AXIS_X "x" +#define DEF_BAR_AXIS_Y "y" +#define DEF_BAR_BACKGROUND "navyblue" +#define DEF_BAR_BORDERWIDTH "2" +#define DEF_BAR_ERRORBAR_COLOR "defcolor" +#define DEF_BAR_ERRORBAR_LINE_WIDTH "1" +#define DEF_BAR_ERRORBAR_CAP_WIDTH "1" +#define DEF_BAR_FOREGROUND "blue" +#define DEF_BAR_HIDE "no" +#define DEF_BAR_LABEL_RELIEF "flat" +#define DEF_BAR_NORMAL_STIPPLE "" +#define DEF_BAR_RELIEF "raised" +#define DEF_BAR_SHOW_ERRORBARS "both" +#define DEF_BAR_STATE "normal" +#define DEF_BAR_STACK (char *)NULL +#define DEF_BAR_STYLES "" +#define DEF_BAR_TAGS "all" +#define DEF_BAR_WIDTH "0.0" + +#define DEF_PEN_ACTIVE_BACKGROUND "red" +#define DEF_PEN_ACTIVE_FOREGROUND "pink" +#define DEF_PEN_BORDERWIDTH "2" +#define DEF_PEN_NORMAL_BACKGROUND "navyblue" +#define DEF_PEN_NORMAL_FOREGROUND "blue" +#define DEF_PEN_RELIEF "raised" +#define DEF_PEN_STIPPLE "" +#define DEF_PEN_TYPE "bar" +#define DEF_PEN_VALUE_ANCHOR "s" +#define DEF_PEN_VALUE_COLOR RGB_BLACK +#define DEF_PEN_VALUE_FONT STD_FONT_SMALL +#define DEF_PEN_VALUE_FORMAT "%g" +#define DEF_PEN_SHOW_VALUES "no" + +static Blt_ConfigSpec barPenConfigSpecs[] = +{ + {BLT_CONFIG_BACKGROUND, "-background", "background", "Background", + DEF_PEN_ACTIVE_BACKGROUND, Blt_Offset(BarPen, fill), + BLT_CONFIG_NULL_OK | ACTIVE_PEN}, + {BLT_CONFIG_BACKGROUND, "-background", "background", "Background", + DEF_PEN_NORMAL_BACKGROUND, Blt_Offset(BarPen, fill), + BLT_CONFIG_NULL_OK | NORMAL_PEN}, + {BLT_CONFIG_SYNONYM, "-bd", "borderWidth", (char *)NULL, + (char *)NULL, 0, ALL_PENS}, + {BLT_CONFIG_SYNONYM, "-bg", "background", (char *)NULL, + (char *)NULL, 0, ALL_PENS}, + {BLT_CONFIG_PIXELS_NNEG, "-borderwidth", "borderWidth", "BorderWidth", + DEF_PEN_BORDERWIDTH, Blt_Offset(BarPen, borderWidth), ALL_PENS}, + {BLT_CONFIG_CUSTOM, "-errorbarcolor", "errorBarColor", "ErrorBarColor", + DEF_BAR_ERRORBAR_COLOR, Blt_Offset(BarPen, errorBarColor), ALL_PENS, + &bltColorOption}, + {BLT_CONFIG_PIXELS_NNEG, "-errorbarwidth", "errorBarWidth","ErrorBarWidth", + DEF_BAR_ERRORBAR_LINE_WIDTH, Blt_Offset(BarPen, errorBarLineWidth), + ALL_PENS | BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_PIXELS_NNEG, "-errorbarcap", "errorBarCap", "ErrorBarCap", + DEF_BAR_ERRORBAR_CAP_WIDTH, Blt_Offset(BarPen, errorBarCapWidth), + ALL_PENS | BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_SYNONYM, "-fg", "foreground", (char *)NULL, + (char *)NULL, 0, ALL_PENS}, + {BLT_CONFIG_SYNONYM, "-fill", "background", (char *)NULL, + (char *)NULL, 0, ALL_PENS}, + {BLT_CONFIG_COLOR, "-foreground", "foreground", "Foreground", + DEF_PEN_ACTIVE_FOREGROUND, Blt_Offset(BarPen, outlineColor), + ACTIVE_PEN | BLT_CONFIG_NULL_OK}, + {BLT_CONFIG_COLOR, "-foreground", "foreground", "Foreground", + DEF_PEN_NORMAL_FOREGROUND, Blt_Offset(BarPen, outlineColor), + NORMAL_PEN | BLT_CONFIG_NULL_OK}, + {BLT_CONFIG_SYNONYM, "-outline", "foreground", (char *)NULL, + (char *)NULL, 0, ALL_PENS}, + {BLT_CONFIG_RELIEF, "-relief", "relief", "Relief", + DEF_PEN_RELIEF, Blt_Offset(BarPen, relief), ALL_PENS}, + {BLT_CONFIG_FILL, "-showerrorbars", "showErrorBars", "ShowErrorBars", + DEF_BAR_SHOW_ERRORBARS, Blt_Offset(BarPen, errorBarShow), + BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_FILL, "-showvalues", "showValues", "ShowValues", + DEF_PEN_SHOW_VALUES, Blt_Offset(BarPen, valueShow), + ALL_PENS | BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_BITMAP, "-stipple", "stipple", "Stipple", DEF_PEN_STIPPLE, + Blt_Offset(BarPen, stipple), ALL_PENS | BLT_CONFIG_NULL_OK}, + {BLT_CONFIG_STRING, "-type", (char *)NULL, (char *)NULL, DEF_PEN_TYPE, + Blt_Offset(BarPen, typeId), ALL_PENS | BLT_CONFIG_NULL_OK}, + {BLT_CONFIG_ANCHOR, "-valueanchor", "valueAnchor", "ValueAnchor", + DEF_PEN_VALUE_ANCHOR, Blt_Offset(BarPen, valueStyle.anchor), + ALL_PENS}, + {BLT_CONFIG_COLOR, "-valuecolor", "valueColor", "ValueColor", + DEF_PEN_VALUE_COLOR, Blt_Offset(BarPen, valueStyle.color), + ALL_PENS}, + {BLT_CONFIG_FONT, "-valuefont", "valueFont", "ValueFont", + DEF_PEN_VALUE_FONT, Blt_Offset(BarPen, valueStyle.font), + ALL_PENS}, + {BLT_CONFIG_STRING, "-valueformat", "valueFormat", "ValueFormat", + DEF_PEN_VALUE_FORMAT, Blt_Offset(BarPen, valueFormat), + ALL_PENS | BLT_CONFIG_NULL_OK}, + {BLT_CONFIG_FLOAT, "-valuerotate", "valueRotate", "ValueRotate", + (char *)NULL, Blt_Offset(BarPen, valueStyle.angle), ALL_PENS}, + {BLT_CONFIG_END, NULL, NULL, NULL, NULL, 0, 0} +}; + + +static Blt_ConfigSpec barElemConfigSpecs[] = { + {BLT_CONFIG_CUSTOM, "-activepen", "activePen", "ActivePen", + DEF_BAR_ACTIVE_PEN, Blt_Offset(BarElement, activePenPtr), + BLT_CONFIG_NULL_OK, &bltBarPenOption}, + {BLT_CONFIG_BACKGROUND, "-background", "background", "Background", + DEF_BAR_BACKGROUND, Blt_Offset(BarElement, builtinPen.fill), + BLT_CONFIG_NULL_OK}, + {BLT_CONFIG_FLOAT, "-barwidth", "barWidth", "BarWidth", + DEF_BAR_WIDTH, Blt_Offset(BarElement, barWidth), + BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_SYNONYM, "-bd", "borderWidth", (char *)NULL, + (char *)NULL, 0, 0}, + {BLT_CONFIG_SYNONYM, "-bg", "background", (char *)NULL, + (char *)NULL, 0, 0}, + {BLT_CONFIG_LIST, "-bindtags", "bindTags", "BindTags", DEF_BAR_TAGS, + Blt_Offset(BarElement, obj.tags), BLT_CONFIG_NULL_OK}, + {BLT_CONFIG_PIXELS_NNEG, "-borderwidth", "borderWidth", "BorderWidth", + DEF_BAR_BORDERWIDTH, Blt_Offset(BarElement, builtinPen.borderWidth), 0}, + {BLT_CONFIG_SYNONYM, "-color", "background", (char *)NULL, + (char *)NULL, 0, 0}, + {BLT_CONFIG_CUSTOM, "-errorbarcolor", "errorBarColor", "ErrorBarColor", + DEF_BAR_ERRORBAR_COLOR, + Blt_Offset(BarElement, builtinPen.errorBarColor), 0, &bltColorOption}, + {BLT_CONFIG_PIXELS_NNEG,"-errorbarwidth", "errorBarWidth", "ErrorBarWidth", + DEF_BAR_ERRORBAR_LINE_WIDTH, + Blt_Offset(BarElement, builtinPen.errorBarLineWidth), + BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_PIXELS_NNEG, "-errorbarcap", "errorBarCap", "ErrorBarCap", + DEF_BAR_ERRORBAR_CAP_WIDTH, + Blt_Offset(BarElement, builtinPen.errorBarCapWidth), + BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_SYNONYM, "-fg", "foreground", (char *)NULL, (char *)NULL, 0, 0}, + {BLT_CONFIG_CUSTOM, "-data", "data", "Data", (char *)NULL, 0, 0, + &bltValuePairsOption}, + {BLT_CONFIG_SYNONYM, "-fill", "background", (char *)NULL, + (char *)NULL, 0, 0}, + {BLT_CONFIG_COLOR, "-foreground", "foreground", "Foreground", + DEF_BAR_FOREGROUND, Blt_Offset(BarElement, builtinPen.outlineColor), + BLT_CONFIG_NULL_OK}, + {BLT_CONFIG_STRING, "-label", "label", "Label", (char *)NULL, + Blt_Offset(BarElement, label), BLT_CONFIG_NULL_OK}, + {BLT_CONFIG_RELIEF, "-legendrelief", "legendRelief", "LegendRelief", + DEF_BAR_LABEL_RELIEF, Blt_Offset(BarElement, legendRelief), + BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_BITMASK, "-hide", "hide", "Hide", DEF_BAR_HIDE, + Blt_Offset(BarElement, flags), BLT_CONFIG_DONT_SET_DEFAULT, + (Blt_CustomOption *)HIDE}, + {BLT_CONFIG_CUSTOM, "-mapx", "mapX", "MapX", DEF_BAR_AXIS_X, + Blt_Offset(BarElement, axes.x), 0, &bltXAxisOption}, + {BLT_CONFIG_CUSTOM, "-mapy", "mapY", "MapY", DEF_BAR_AXIS_Y, + Blt_Offset(BarElement, axes.y), 0, &bltYAxisOption}, + {BLT_CONFIG_SYNONYM, "-outline", "foreground", (char *)NULL, + (char *)NULL, 0, 0}, + {BLT_CONFIG_CUSTOM, "-pen", "pen", "Pen", (char *)NULL, + Blt_Offset(BarElement, normalPenPtr), BLT_CONFIG_NULL_OK, + &bltBarPenOption}, + {BLT_CONFIG_RELIEF, "-relief", "relief", "Relief", + DEF_BAR_RELIEF, Blt_Offset(BarElement, builtinPen.relief), 0}, + {BLT_CONFIG_FILL, "-showerrorbars", "showErrorBars", "ShowErrorBars", + DEF_BAR_SHOW_ERRORBARS, Blt_Offset(BarElement, builtinPen.errorBarShow), + BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_FILL, "-showvalues", "showValues", "ShowValues", + DEF_PEN_SHOW_VALUES, Blt_Offset(BarElement, builtinPen.valueShow), + BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_STRING, "-stack", "stack", "Stack", DEF_BAR_STACK, + Blt_Offset(BarElement, groupName), BLT_CONFIG_NULL_OK}, + {BLT_CONFIG_STATE, "-state", "state", "State", DEF_BAR_STATE, + Blt_Offset(BarElement, state), BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_BITMAP, "-stipple", "stipple", "Stipple", + DEF_BAR_NORMAL_STIPPLE, Blt_Offset(BarElement, builtinPen.stipple), + BLT_CONFIG_NULL_OK}, + {BLT_CONFIG_CUSTOM, "-styles", "styles", "Styles", DEF_BAR_STYLES, + Blt_Offset(BarElement, stylePalette), 0, &bltBarStylesOption}, + {BLT_CONFIG_ANCHOR, "-valueanchor", "valueAnchor", "ValueAnchor", + DEF_PEN_VALUE_ANCHOR, + Blt_Offset(BarElement, builtinPen.valueStyle.anchor), 0}, + {BLT_CONFIG_COLOR, "-valuecolor", "valueColor", "ValueColor", + DEF_PEN_VALUE_COLOR, + Blt_Offset(BarElement, builtinPen.valueStyle.color), 0}, + {BLT_CONFIG_FONT, "-valuefont", "valueFont", "ValueFont", + DEF_PEN_VALUE_FONT, + Blt_Offset(BarElement, builtinPen.valueStyle.font), 0}, + {BLT_CONFIG_STRING, "-valueformat", "valueFormat", "ValueFormat", + DEF_PEN_VALUE_FORMAT, Blt_Offset(BarElement, builtinPen.valueFormat), + BLT_CONFIG_NULL_OK}, + {BLT_CONFIG_FLOAT, "-valuerotate", "valueRotate", "ValueRotate", + (char *)NULL, Blt_Offset(BarElement, builtinPen.valueStyle.angle), 0}, + {BLT_CONFIG_CUSTOM, "-weights", "weights", "Weights", (char *)NULL, + Blt_Offset(BarElement, w), 0, &bltValuesOption}, + {BLT_CONFIG_CUSTOM, "-x", "xdata", "Xdata", (char *)NULL, + Blt_Offset(BarElement, x), 0, &bltValuesOption}, + {BLT_CONFIG_CUSTOM, "-y", "ydata", "Ydata", (char *)NULL, + Blt_Offset(BarElement, y), 0, &bltValuesOption}, + {BLT_CONFIG_CUSTOM, "-xdata", "xdata", "Xdata", (char *)NULL, + Blt_Offset(BarElement, x), 0, &bltValuesOption}, + {BLT_CONFIG_CUSTOM, "-ydata", "ydata", "Ydata", (char *)NULL, + Blt_Offset(BarElement, y), 0, &bltValuesOption}, + {BLT_CONFIG_CUSTOM, "-xerror", "xError", "XError", (char *)NULL, + Blt_Offset(BarElement, xError), 0, &bltValuesOption}, + {BLT_CONFIG_CUSTOM, "-xhigh", "xHigh", "XHigh", (char *)NULL, + Blt_Offset(BarElement, xHigh), 0, &bltValuesOption}, + {BLT_CONFIG_CUSTOM, "-xlow", "xLow", "XLow", (char *)NULL, + Blt_Offset(BarElement, xLow), 0, &bltValuesOption}, + {BLT_CONFIG_CUSTOM, "-yerror", "yError", "YError", (char *)NULL, + Blt_Offset(BarElement, yError), 0, &bltValuesOption}, + {BLT_CONFIG_CUSTOM, "-yhigh", "yHigh", "YHigh", (char *)NULL, + Blt_Offset(BarElement, yHigh), 0, &bltValuesOption}, + {BLT_CONFIG_CUSTOM, "-ylow", "yLow", "YLow", (char *)NULL, + Blt_Offset(BarElement, yLow), 0, &bltValuesOption}, + {BLT_CONFIG_END, NULL, NULL, NULL, NULL, 0, 0} +}; + +/* Forward declarations */ +static PenConfigureProc ConfigureBarPenProc; +static PenDestroyProc DestroyBarPenProc; +static ElementClosestProc ClosestBarProc; +static ElementConfigProc ConfigureBarProc; +static ElementDestroyProc DestroyBarProc; +static ElementDrawProc DrawActiveBarProc; +static ElementDrawProc DrawNormalBarProc; +static ElementDrawSymbolProc DrawSymbolProc; +static ElementExtentsProc GetBarExtentsProc; +static ElementToPostScriptProc ActiveBarToPostScriptProc; +static ElementToPostScriptProc NormalBarToPostScriptProc; +static ElementSymbolToPostScriptProc SymbolToPostScriptProc; +static ElementMapProc MapBarProc; + +INLINE static int +Round(double x) +{ + return (int) (x + ((x < 0.0) ? -0.5 : 0.5)); +} + +/* + *--------------------------------------------------------------------------- + * Custom option parse and print procedures + *--------------------------------------------------------------------------- + */ + +/* + *--------------------------------------------------------------------------- + * + * NameOfBarMode -- + * + * Converts the integer representing the mode style into a string. + * + *--------------------------------------------------------------------------- + */ +static const char * +NameOfBarMode(BarMode mode) +{ + switch (mode) { + case BARS_INFRONT: + return "infront"; + case BARS_OVERLAP: + return "overlap"; + case BARS_STACKED: + return "stacked"; + case BARS_ALIGNED: + return "aligned"; + default: + return "unknown mode value"; + } +} + +/* + *--------------------------------------------------------------------------- + * + * ObjToMode -- + * + * Converts the mode string into its numeric representation. + * + * Valid mode strings are: + * + * "infront" Draw a full bar at each point in the element. + * + * "stacked" Stack bar segments vertically. Each stack is defined + * by each ordinate at a particular abscissa. The height + * of each segment is represented by the sum the previous + * ordinates. + * + * "aligned" Align bar segments as smaller slices one next to + * the other. Like "stacks", aligned segments are + * defined by each ordinate at a particular abscissa. + * + * Results: + * A standard TCL result. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +ObjToBarMode( + ClientData clientData, /* Not used. */ + Tcl_Interp *interp, /* Interpreter to send results back + * to */ + Tk_Window tkwin, /* Not used. */ + Tcl_Obj *objPtr, /* Mode style string */ + char *widgRec, /* Cubicle structure record */ + int offset, /* Offset to field in structure */ + int flags) /* Not used. */ +{ + BarMode *modePtr = (BarMode *)(widgRec + offset); + int length; + char c; + char *string; + + string = Tcl_GetStringFromObj(objPtr, &length); + c = string[0]; + if ((c == 'n') && (strncmp(string, "normal", length) == 0)) { + *modePtr = BARS_INFRONT; + } else if ((c == 'i') && (strncmp(string, "infront", length) == 0)) { + *modePtr = BARS_INFRONT; + } else if ((c == 's') && (strncmp(string, "stacked", length) == 0)) { + *modePtr = BARS_STACKED; + } else if ((c == 'a') && (strncmp(string, "aligned", length) == 0)) { + *modePtr = BARS_ALIGNED; + } else if ((c == 'o') && (strncmp(string, "overlap", length) == 0)) { + *modePtr = BARS_OVERLAP; + } else { + Tcl_AppendResult(interp, "bad mode argument \"", string, "\": should" + "be \"infront\", \"stacked\", \"overlap\", or \"aligned\"", + (char *)NULL); + return TCL_ERROR; + } + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * BarModeToObj -- + * + * Returns the mode style string based upon the mode flags. + * + * Results: + * The mode style string is returned. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static Tcl_Obj * +BarModeToObj( + ClientData clientData, /* Not used. */ + Tcl_Interp *interp, /* Not used. */ + Tk_Window tkwin, /* Not used. */ + char *widgRec, /* Row/column structure record */ + int offset, /* Offset to field in structure */ + int flags) /* Not used. */ +{ + BarMode mode = *(BarMode *)(widgRec + offset); + + return Tcl_NewStringObj(NameOfBarMode(mode), -1); +} + + +/* + * Zero out the style's number of bars and errorbars. + */ +static void +ResetStylePalette(Blt_Chain stylePalette) +{ + Blt_ChainLink link; + + for (link = Blt_Chain_FirstLink(stylePalette); link != NULL; + link = Blt_Chain_NextLink(link)) { + BarStyle *stylePtr; + + stylePtr = Blt_Chain_GetValue(link); + stylePtr->xeb.length = stylePtr->yeb.length = 0; + stylePtr->nBars = 0; + } +} + +static int +ConfigureBarPen(Graph *graphPtr, BarPen *penPtr) +{ + XGCValues gcValues; + unsigned long gcMask; + GC newGC; + long defColor; + int screenNum; + + screenNum = Tk_ScreenNumber(graphPtr->tkwin); + gcMask = GCForeground | GCLineWidth; + gcValues.line_width = LineWidth(penPtr->errorBarLineWidth); + + if (penPtr->outlineColor != NULL) { + defColor = penPtr->outlineColor->pixel; + gcValues.foreground = penPtr->outlineColor->pixel; + } else if (penPtr->fill != NULL) { + defColor = Blt_BackgroundBorderColor(penPtr->fill)->pixel; + gcValues.foreground = defColor; + } else { + defColor = BlackPixel(graphPtr->display, screenNum); + } + newGC = Tk_GetGC(graphPtr->tkwin, gcMask, &gcValues); + if (penPtr->outlineGC != NULL) { + Tk_FreeGC(graphPtr->display, penPtr->outlineGC); + } + penPtr->outlineGC = newGC; + + newGC = NULL; + if (penPtr->stipple != None) { + /* Handle old-style -stipple specially. */ + gcMask = GCForeground | GCBackground | GCFillStyle | GCStipple; + gcValues.foreground = BlackPixel(graphPtr->display, screenNum); + gcValues.background = WhitePixel(graphPtr->display, screenNum); + if (penPtr->fill != NULL) { + gcValues.foreground = + Blt_BackgroundBorderColor(penPtr->fill)->pixel; + } else if (penPtr->outlineColor != NULL) { + gcValues.foreground = penPtr->outlineColor->pixel; + } + gcValues.stipple = penPtr->stipple; + gcValues.fill_style = FillStippled; + newGC = Tk_GetGC(graphPtr->tkwin, gcMask, &gcValues); + } + if (penPtr->fillGC != NULL) { + Tk_FreeGC(graphPtr->display, penPtr->fillGC); + } + penPtr->fillGC = newGC; + + gcMask = GCForeground | GCLineWidth; + if (penPtr->errorBarColor == COLOR_DEFAULT) { + gcValues.foreground = defColor; + } else { + gcValues.foreground = penPtr->errorBarColor->pixel; + } + gcValues.line_width = LineWidth(penPtr->errorBarLineWidth); + newGC = Tk_GetGC(graphPtr->tkwin, gcMask, &gcValues); + if (penPtr->errorBarGC != NULL) { + Tk_FreeGC(graphPtr->display, penPtr->errorBarGC); + } + penPtr->errorBarGC = newGC; + return TCL_OK; +} + +static void +DestroyBarPen(Graph *graphPtr, BarPen *penPtr) +{ + Blt_Ts_FreeStyle(graphPtr->display, &penPtr->valueStyle); + if (penPtr->outlineGC != NULL) { + Tk_FreeGC(graphPtr->display, penPtr->outlineGC); + } + if (penPtr->fillGC != NULL) { + Tk_FreeGC(graphPtr->display, penPtr->fillGC); + } + if (penPtr->errorBarGC != NULL) { + Tk_FreeGC(graphPtr->display, penPtr->errorBarGC); + } +} + +static int +ConfigureBarPenProc(Graph *graphPtr, Pen *basePtr) +{ + return ConfigureBarPen(graphPtr, (BarPen *)basePtr); +} + +static void +DestroyBarPenProc(Graph *graphPtr, Pen *basePtr) +{ + DestroyBarPen(graphPtr, (BarPen *)basePtr); +} + + +static void +InitializeBarPen(BarPen *penPtr) +{ + /* Generic fields common to all pen types. */ + penPtr->configProc = ConfigureBarPenProc; + penPtr->destroyProc = DestroyBarPenProc; + penPtr->flags = NORMAL_PEN; + penPtr->configSpecs = barPenConfigSpecs; + + /* Initialize fields specific to bar pens. */ + Blt_Ts_InitStyle(penPtr->valueStyle); + penPtr->relief = TK_RELIEF_RAISED; + penPtr->valueShow = SHOW_NONE; + penPtr->borderWidth = 2; + penPtr->errorBarShow = SHOW_BOTH; +} + +Pen * +Blt_BarPen(const char *penName) +{ + BarPen *penPtr; + + penPtr = Blt_AssertCalloc(1, sizeof(BarPen)); + InitializeBarPen(penPtr); + penPtr->name = Blt_AssertStrdup(penName); + if (strcmp(penName, "activeBar") == 0) { + penPtr->flags = ACTIVE_PEN; + } + return (Pen *)penPtr; +} + +/* + *--------------------------------------------------------------------------- + * + * CheckBarStacks -- + * + * Check that the data limits are not superseded by the heights of + * stacked bar segments. The heights are calculated by + * Blt_ComputeStacks. + * + * Results: + * If the y-axis limits need to be adjusted for stacked segments, + * *minPtr* or *maxPtr* are updated. + * + * Side effects: + * Autoscaling of the y-axis is affected. + * + *--------------------------------------------------------------------------- + */ +static void +CheckBarStacks(Graph *graphPtr, Axis2d *pairPtr, double *minPtr, double *maxPtr) +{ + BarGroup *gp, *gend; + + if ((graphPtr->mode != BARS_STACKED) || (graphPtr->nBarGroups == 0)) { + return; + } + for (gp = graphPtr->barGroups, gend = gp + graphPtr->nBarGroups; gp < gend; + gp++) { + if ((gp->axes.x == pairPtr->x) && (gp->axes.y == pairPtr->y)) { + /* + * Check if any of the y-values (because of stacking) are greater + * than the current limits of the graph. + */ + if (gp->sum < 0.0f) { + if (*minPtr > gp->sum) { + *minPtr = gp->sum; + } + } else { + if (*maxPtr < gp->sum) { + *maxPtr = gp->sum; + } + } + } + } +} + +/* + *--------------------------------------------------------------------------- + * + * ConfigureBarProc -- + * + * Sets up the appropriate configuration parameters in the GC. It is + * assumed the parameters have been previously set by a call to + * Blt_ConfigureWidget. + * + * Results: + * The return value is a standard TCL result. If TCL_ERROR is returned, + * then interp->result contains an error message. + * + * Side effects: + * Configuration information such as bar foreground/background color and + * stipple etc. get set in a new GC. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +ConfigureBarProc(Graph *graphPtr, Element *basePtr) +{ + BarElement *elemPtr = (BarElement *)basePtr; + Blt_ChainLink link; + BarStyle *stylePtr; + + if (ConfigureBarPen(graphPtr, elemPtr->builtinPenPtr)!= TCL_OK) { + return TCL_ERROR; + } + /* + * Point to the static normal pen if no external pens have been selected. + */ + link = Blt_Chain_FirstLink(elemPtr->stylePalette); + if (link == NULL) { + link = Blt_Chain_AllocLink(sizeof(BarStyle)); + Blt_Chain_LinkAfter(elemPtr->stylePalette, link, NULL); + } + stylePtr = Blt_Chain_GetValue(link); + stylePtr->penPtr = NORMALPEN(elemPtr); + + if (Blt_ConfigModified(elemPtr->configSpecs, "-barwidth", "-*data", + "-map*", "-label", "-hide", "-x", "-y", (char *)NULL)) { + elemPtr->flags |= MAP_ITEM; + } + return TCL_OK; +} + +static void +GetBarExtentsProc(Element *basePtr, Region2d *regPtr) +{ + BarElement *elemPtr = (BarElement *)basePtr; + Graph *graphPtr; + double middle, barWidth; + int nPoints; + + graphPtr = elemPtr->obj.graphPtr; + regPtr->top = regPtr->left = DBL_MAX; + regPtr->bottom = regPtr->right = -DBL_MAX; + + nPoints = NUMBEROFPOINTS(elemPtr); + if (nPoints < 1) { + return; /* No data points */ + } + barWidth = graphPtr->barWidth; + if (elemPtr->barWidth > 0.0f) { + barWidth = elemPtr->barWidth; + } + middle = 0.5; + regPtr->left = elemPtr->x.min - middle; + regPtr->right = elemPtr->x.max + middle; + + regPtr->top = elemPtr->y.min; + regPtr->bottom = elemPtr->y.max; + if (regPtr->bottom < graphPtr->baseline) { + regPtr->bottom = graphPtr->baseline; + } + /* + * Handle stacked bar elements specially. + * + * If element is stacked, the sum of its ordinates may be outside the + * minimum/maximum limits of the element's data points. + */ + if ((graphPtr->mode == BARS_STACKED) && (graphPtr->nBarGroups > 0)) { + CheckBarStacks(graphPtr, &elemPtr->axes, ®Ptr->top, ®Ptr->bottom); + } + /* Warning: You get what you deserve if the x-axis is logScale */ + if (elemPtr->axes.x->logScale) { + regPtr->left = Blt_FindElemValuesMinimum(&elemPtr->x, DBL_MIN) + + middle; + } + /* Fix y-min limits for barchart */ + if (elemPtr->axes.y->logScale) { + if ((regPtr->top <= 0.0) || (regPtr->top > 1.0)) { + regPtr->top = 1.0; + } + } else { + if (regPtr->top > 0.0) { + regPtr->top = 0.0; + } + } + /* Correct the extents for error bars if they exist. */ + if (elemPtr->xError.nValues > 0) { + int i; + + /* Correct the data limits for error bars */ + nPoints = MIN(elemPtr->xError.nValues, nPoints); + for (i = 0; i < nPoints; i++) { + double x; + + x = elemPtr->x.values[i] + elemPtr->xError.values[i]; + if (x > regPtr->right) { + regPtr->right = x; + } + x = elemPtr->x.values[i] - elemPtr->xError.values[i]; + if (elemPtr->axes.x->logScale) { + if (x < 0.0) { + x = -x; /* Mirror negative values, instead of + * ignoring them. */ + } + if ((x > DBL_MIN) && (x < regPtr->left)) { + regPtr->left = x; + } + } else if (x < regPtr->left) { + regPtr->left = x; + } + } + } else { + if ((elemPtr->xHigh.nValues > 0) && + (elemPtr->xHigh.max > regPtr->right)) { + regPtr->right = elemPtr->xHigh.max; + } + if (elemPtr->xLow.nValues > 0) { + double left; + + if ((elemPtr->xLow.min <= 0.0) && + (elemPtr->axes.x->logScale)) { + left = Blt_FindElemValuesMinimum(&elemPtr->xLow, DBL_MIN); + } else { + left = elemPtr->xLow.min; + } + if (left < regPtr->left) { + regPtr->left = left; + } + } + } + if (elemPtr->yError.nValues > 0) { + int i; + + nPoints = MIN(elemPtr->yError.nValues, nPoints); + for (i = 0; i < nPoints; i++) { + double y; + + y = elemPtr->y.values[i] + elemPtr->yError.values[i]; + if (y > regPtr->bottom) { + regPtr->bottom = y; + } + y = elemPtr->y.values[i] - elemPtr->yError.values[i]; + if (elemPtr->axes.y->logScale) { + if (y < 0.0) { + y = -y; /* Mirror negative values, instead of + * ignoring them. */ + } + if ((y > DBL_MIN) && (y < regPtr->left)) { + regPtr->top = y; + } + } else if (y < regPtr->top) { + regPtr->top = y; + } + } + } else { + if ((elemPtr->yHigh.nValues > 0) && + (elemPtr->yHigh.max > regPtr->bottom)) { + regPtr->bottom = elemPtr->yHigh.max; + } + if (elemPtr->yLow.nValues > 0) { + double top; + + if ((elemPtr->yLow.min <= 0.0) && + (elemPtr->axes.y->logScale)) { + top = Blt_FindElemValuesMinimum(&elemPtr->yLow, DBL_MIN); + } else { + top = elemPtr->yLow.min; + } + if (top < regPtr->top) { + regPtr->top = top; + } + } + } +} + +/* + *--------------------------------------------------------------------------- + * + * ClosestBar -- + * + * Find the bar segment closest to the window coordinates point + * specified. + * + * Note: This does not return the height of the stacked segment + * (in graph coordinates) properly. + * + * Results: + * Returns 1 if the point is width any bar segment, otherwise 0. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static void +ClosestBarProc( + Graph *graphPtr, /* Not used. */ + Element *basePtr, /* Bar element */ + ClosestSearch *searchPtr) /* Information about closest point in + * element */ +{ + BarElement *elemPtr = (BarElement *)basePtr; + XRectangle *bp; + double minDist; + int imin; + int i; + + minDist = searchPtr->dist; + imin = 0; + + for (bp = elemPtr->bars, i = 0; i < elemPtr->nBars; i++, bp++) { + Point2d *pp, *pend; + Point2d outline[5]; + double left, right, top, bottom; + + if (PointInRectangle(bp, searchPtr->x, searchPtr->y)) { + imin = elemPtr->barToData[i]; + minDist = 0.0; + break; + } + left = bp->x, top = bp->y; + right = (double)(bp->x + bp->width); + bottom = (double)(bp->y + bp->height); + outline[4].x = outline[3].x = outline[0].x = left; + outline[4].y = outline[1].y = outline[0].y = top; + outline[2].x = outline[1].x = right; + outline[3].y = outline[2].y = bottom; + + for (pp = outline, pend = outline + 4; pp < pend; pp++) { + Point2d t; + double dist; + + t = Blt_GetProjection(searchPtr->x, searchPtr->y, pp, pp + 1); + if (t.x > right) { + t.x = right; + } else if (t.x < left) { + t.x = left; + } + if (t.y > bottom) { + t.y = bottom; + } else if (t.y < top) { + t.y = top; + } + dist = hypot((t.x - searchPtr->x), (t.y - searchPtr->y)); + if (dist < minDist) { + minDist = dist; + imin = elemPtr->barToData[i]; + } + } + } + if (minDist < searchPtr->dist) { + searchPtr->elemPtr = (Element *)elemPtr; + searchPtr->dist = minDist; + searchPtr->index = imin; + searchPtr->point.x = (double)elemPtr->x.values[imin]; + searchPtr->point.y = (double)elemPtr->y.values[imin]; + } +} + +/* + *--------------------------------------------------------------------------- + * + * MergePens -- + * + * Reorders the both arrays of points and errorbars to merge pens. + * + * Results: + * None. + * + * Side effects: + * The old arrays are freed and new ones allocated containing + * the reordered points and errorbars. + * + *--------------------------------------------------------------------------- + */ +static void +MergePens(BarElement *elemPtr, BarStyle **dataToStyle) +{ + if (Blt_Chain_GetLength(elemPtr->stylePalette) < 2) { + Blt_ChainLink link; + BarStyle *stylePtr; + + link = Blt_Chain_FirstLink(elemPtr->stylePalette); + + stylePtr = Blt_Chain_GetValue(link); + stylePtr->nBars = elemPtr->nBars; + stylePtr->bars = elemPtr->bars; + stylePtr->symbolSize = elemPtr->bars->width / 2; + stylePtr->xeb.length = elemPtr->xeb.length; + stylePtr->xeb.segments = elemPtr->xeb.segments; + stylePtr->yeb.length = elemPtr->yeb.length; + stylePtr->yeb.segments = elemPtr->yeb.segments; + return; + } + /* We have more than one style. Group bar segments of like pen styles + * together. */ + + if (elemPtr->nBars > 0) { + Blt_ChainLink link; + XRectangle *bars, *bp; + int *ip, *barToData; + + bars = Blt_AssertMalloc(elemPtr->nBars * sizeof(XRectangle)); + barToData = Blt_AssertMalloc(elemPtr->nBars * sizeof(int)); + bp = bars, ip = barToData; + for (link = Blt_Chain_FirstLink(elemPtr->stylePalette); link != NULL; + link = Blt_Chain_NextLink(link)) { + BarStyle *stylePtr; + int i; + + stylePtr = Blt_Chain_GetValue(link); + stylePtr->symbolSize = bp->width / 2; + stylePtr->bars = bp; + for (i = 0; i < elemPtr->nBars; i++) { + int iData; + + iData = elemPtr->barToData[i]; + if (dataToStyle[iData] == stylePtr) { + *bp++ = elemPtr->bars[i]; + *ip++ = iData; + } + } + stylePtr->nBars = bp - stylePtr->bars; + } + Blt_Free(elemPtr->bars); + Blt_Free(elemPtr->barToData); + elemPtr->bars = bars; + elemPtr->barToData = barToData; + } + + if (elemPtr->xeb.length > 0) { + Blt_ChainLink link; + Segment2d *bars, *sp; + int *map, *ip; + + bars = Blt_AssertMalloc(elemPtr->xeb.length * sizeof(Segment2d)); + map = Blt_AssertMalloc(elemPtr->xeb.length * sizeof(int)); + sp = bars, ip = map; + for (link = Blt_Chain_FirstLink(elemPtr->stylePalette); + link != NULL; link = Blt_Chain_NextLink(link)) { + BarStyle *stylePtr; + int i; + + stylePtr = Blt_Chain_GetValue(link); + stylePtr->xeb.segments = sp; + for (i = 0; i < elemPtr->xeb.length; i++) { + int iData; + + iData = elemPtr->xeb.map[i]; + if (dataToStyle[iData] == stylePtr) { + *sp++ = elemPtr->xeb.segments[i]; + *ip++ = iData; + } + } + stylePtr->xeb.length = sp - stylePtr->xeb.segments; + } + Blt_Free(elemPtr->xeb.segments); + elemPtr->xeb.segments = bars; + Blt_Free(elemPtr->xeb.map); + elemPtr->xeb.map = map; + } + if (elemPtr->yeb.length > 0) { + Blt_ChainLink link; + Segment2d *bars, *sp; + int *map, *ip; + + bars = Blt_AssertMalloc(elemPtr->yeb.length * sizeof(Segment2d)); + map = Blt_AssertMalloc(elemPtr->yeb.length * sizeof(int)); + sp = bars, ip = map; + for (link = Blt_Chain_FirstLink(elemPtr->stylePalette); link != NULL; + link = Blt_Chain_NextLink(link)) { + BarStyle *stylePtr; + int i; + + stylePtr = Blt_Chain_GetValue(link); + stylePtr->yeb.segments = sp; + for (i = 0; i < elemPtr->yeb.length; i++) { + int iData; + + iData = elemPtr->yeb.map[i]; + if (dataToStyle[iData] == stylePtr) { + *sp++ = elemPtr->yeb.segments[i]; + *ip++ = iData; + } + } + stylePtr->yeb.length = sp - stylePtr->yeb.segments; + } + Blt_Free(elemPtr->yeb.segments); + elemPtr->yeb.segments = bars; + Blt_Free(elemPtr->yeb.map); + elemPtr->yeb.map = map; + } +} + +/* + *--------------------------------------------------------------------------- + * + * MapActiveBars -- + * + * Creates an array of points of the active graph coordinates. + * + * Results: + * None. + * + * Side effects: + * Memory is freed and allocated for the active point array. + * + *--------------------------------------------------------------------------- + */ +static void +MapActiveBars(BarElement *elemPtr) +{ + if (elemPtr->activeRects != NULL) { + Blt_Free(elemPtr->activeRects); + elemPtr->activeRects = NULL; + } + if (elemPtr->activeToData != NULL) { + Blt_Free(elemPtr->activeToData); + elemPtr->activeToData = NULL; + } + elemPtr->nActive = 0; + + if (elemPtr->nActiveIndices > 0) { + XRectangle *activeRects; + int *activeToData; + int i; + int count; + + activeRects = Blt_AssertMalloc(sizeof(XRectangle) * + elemPtr->nActiveIndices); + activeToData = Blt_AssertMalloc(sizeof(int) * + elemPtr->nActiveIndices); + count = 0; + for (i = 0; i < elemPtr->nBars; i++) { + int *ip, *iend; + + for (ip = elemPtr->activeIndices, + iend = ip + elemPtr->nActiveIndices; ip < iend; ip++) { + if (elemPtr->barToData[i] == *ip) { + activeRects[count] = elemPtr->bars[i]; + activeToData[count] = i; + count++; + } + } + } + elemPtr->nActive = count; + elemPtr->activeRects = activeRects; + elemPtr->activeToData = activeToData; + } + elemPtr->flags &= ~ACTIVE_PENDING; +} + +static void +ResetBar(BarElement *elemPtr) +{ + /* Release any storage associated with the display of the bar */ + ResetStylePalette(elemPtr->stylePalette); + if (elemPtr->activeRects != NULL) { + Blt_Free(elemPtr->activeRects); + } + if (elemPtr->activeToData != NULL) { + Blt_Free(elemPtr->activeToData); + } + if (elemPtr->xeb.segments != NULL) { + Blt_Free(elemPtr->xeb.segments); + } + if (elemPtr->xeb.map != NULL) { + Blt_Free(elemPtr->xeb.map); + } + if (elemPtr->yeb.segments != NULL) { + Blt_Free(elemPtr->yeb.segments); + } + if (elemPtr->yeb.map != NULL) { + Blt_Free(elemPtr->yeb.map); + } + if (elemPtr->bars != NULL) { + Blt_Free(elemPtr->bars); + } + if (elemPtr->barToData != NULL) { + Blt_Free(elemPtr->barToData); + } + elemPtr->activeToData = elemPtr->xeb.map = elemPtr->yeb.map = + elemPtr->barToData = NULL; + elemPtr->activeRects = elemPtr->bars = NULL; + elemPtr->xeb.segments = elemPtr->yeb.segments = NULL; + elemPtr->nActive = elemPtr->xeb.length = elemPtr->yeb.length = + elemPtr->nBars = 0; +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_MapErrorBars -- + * + * Creates two arrays of points and pen indices, filled with the screen + * coordinates of the visible + * + * Results: + * None. + * + * Side effects: + * Memory is freed and allocated for the index array. + * + *--------------------------------------------------------------------------- + */ +static void +MapErrorBars(Graph *graphPtr, BarElement *elemPtr, BarStyle **dataToStyle) +{ + int n, nPoints; + Region2d reg; + + Blt_GraphExtents(graphPtr, ®); + nPoints = NUMBEROFPOINTS(elemPtr); + if (elemPtr->xError.nValues > 0) { + n = MIN(elemPtr->xError.nValues, nPoints); + } else { + n = MIN3(elemPtr->xHigh.nValues, elemPtr->xLow.nValues, nPoints); + } + if (n > 0) { + Segment2d *bars; + Segment2d *segPtr; + int *map; + int *indexPtr; + int i; + + segPtr = bars = Blt_AssertMalloc(n * 3 * sizeof(Segment2d)); + indexPtr = map = Blt_AssertMalloc(n * 3 * sizeof(int)); + for (i = 0; i < n; i++) { + double x, y; + double high, low; + BarStyle *stylePtr; + + x = elemPtr->x.values[i]; + y = elemPtr->y.values[i]; + stylePtr = dataToStyle[i]; + if ((FINITE(x)) && (FINITE(y))) { + if (elemPtr->xError.nValues > 0) { + high = x + elemPtr->xError.values[i]; + low = x - elemPtr->xError.values[i]; + } else { + high = elemPtr->xHigh.values[i]; + low = elemPtr->xLow.values[i]; + } + if ((FINITE(high)) && (FINITE(low))) { + Point2d p, q; + + p = Blt_Map2D(graphPtr, high, y, &elemPtr->axes); + q = Blt_Map2D(graphPtr, low, y, &elemPtr->axes); + segPtr->p = p; + segPtr->q = q; + if (Blt_LineRectClip(®, &segPtr->p, &segPtr->q)) { + segPtr++; + *indexPtr++ = i; + } + /* Left cap */ + segPtr->p.x = segPtr->q.x = p.x; + segPtr->p.y = p.y - stylePtr->errorBarCapWidth; + segPtr->q.y = p.y + stylePtr->errorBarCapWidth; + if (Blt_LineRectClip(®, &segPtr->p, &segPtr->q)) { + segPtr++; + *indexPtr++ = i; + } + /* Right cap */ + segPtr->p.x = segPtr->q.x = q.x; + segPtr->p.y = q.y - stylePtr->errorBarCapWidth; + segPtr->q.y = q.y + stylePtr->errorBarCapWidth; + if (Blt_LineRectClip(®, &segPtr->p, &segPtr->q)) { + segPtr++; + *indexPtr++ = i; + } + } + } + } + elemPtr->xeb.segments = bars; + elemPtr->xeb.length = segPtr - bars; + elemPtr->xeb.map = map; + } + if (elemPtr->yError.nValues > 0) { + n = MIN(elemPtr->yError.nValues, nPoints); + } else { + n = MIN3(elemPtr->yHigh.nValues, elemPtr->yLow.nValues, nPoints); + } + if (n > 0) { + Segment2d *bars; + Segment2d *segPtr; + int *map; + int *indexPtr; + int i; + + segPtr = bars = Blt_AssertMalloc(n * 3 * sizeof(Segment2d)); + indexPtr = map = Blt_AssertMalloc(n * 3 * sizeof(int)); + for (i = 0; i < n; i++) { + double x, y; + double high, low; + BarStyle *stylePtr; + + x = elemPtr->x.values[i]; + y = elemPtr->y.values[i]; + stylePtr = dataToStyle[i]; + if ((FINITE(x)) && (FINITE(y))) { + if (elemPtr->yError.nValues > 0) { + high = y + elemPtr->yError.values[i]; + low = y - elemPtr->yError.values[i]; + } else { + high = elemPtr->yHigh.values[i]; + low = elemPtr->yLow.values[i]; + } + if ((FINITE(high)) && (FINITE(low))) { + Point2d p, q; + + p = Blt_Map2D(graphPtr, x, high, &elemPtr->axes); + q = Blt_Map2D(graphPtr, x, low, &elemPtr->axes); + segPtr->p = p; + segPtr->q = q; + if (Blt_LineRectClip(®, &segPtr->p, &segPtr->q)) { + segPtr++; + *indexPtr++ = i; + } + /* Top cap. */ + segPtr->p.y = segPtr->q.y = p.y; + segPtr->p.x = p.x - stylePtr->errorBarCapWidth; + segPtr->q.x = p.x + stylePtr->errorBarCapWidth; + if (Blt_LineRectClip(®, &segPtr->p, &segPtr->q)) { + segPtr++; + *indexPtr++ = i; + } + /* Bottom cap. */ + segPtr->p.y = segPtr->q.y = q.y; + segPtr->p.x = q.x - stylePtr->errorBarCapWidth; + segPtr->q.x = q.x + stylePtr->errorBarCapWidth; + if (Blt_LineRectClip(®, &segPtr->p, &segPtr->q)) { + segPtr++; + *indexPtr++ = i; + } + } + } + } + elemPtr->yeb.segments = bars; + elemPtr->yeb.length = segPtr - bars; + elemPtr->yeb.map = map; + } +} + + +/* + *--------------------------------------------------------------------------- + * + * MapBarProc -- + * + * Calculates the actual window coordinates of the bar element. The + * window coordinates are saved in the bar element structure. + * + * Results: + * None. + * + * Notes: + * A bar can have multiple segments (more than one x,y pairs). In this + * case, the bar can be represented as either a set of non-contiguous + * bars or a single multi-segmented (stacked) bar. + * + * The x-axis layout for a barchart may be presented in one of two ways. + * If abscissas are used, the bars are placed at those coordinates. + * Otherwise, the range will represent the number of values. + * + *--------------------------------------------------------------------------- + */ +static void +MapBarProc(Graph *graphPtr, Element *basePtr) +{ + BarElement *elemPtr = (BarElement *)basePtr; + BarStyle **dataToStyle; + double *x, *y; + double barWidth, barOffset; + double baseline, ybot; + int *barToData; /* Maps bars to data point indices */ + int invertBar; + int nPoints, count; + XRectangle *rp, *bars; + int i; + int size; + + ResetBar(elemPtr); + nPoints = NUMBEROFPOINTS(elemPtr); + if (nPoints < 1) { + return; /* No data points */ + } + barWidth = graphPtr->barWidth; + barWidth = (elemPtr->barWidth > 0.0f) + ? elemPtr->barWidth : graphPtr->barWidth; + baseline = (elemPtr->axes.y->logScale) ? 0.0 : graphPtr->baseline; + barOffset = barWidth * 0.5; + + /* + * Create an array of bars representing the screen coordinates of all the + * segments in the bar. + */ + bars = Blt_AssertCalloc(nPoints, sizeof(XRectangle)); + barToData = Blt_AssertCalloc(nPoints, sizeof(int)); + + x = elemPtr->x.values, y = elemPtr->y.values; + count = 0; + for (rp = bars, i = 0; i < nPoints; i++) { + Point2d c1, c2; /* Two opposite corners of the rectangle + * in graph coordinates. */ + double dx, dy; + int height; + double right, left, top, bottom; + + if (((x[i] - barWidth) > elemPtr->axes.x->axisRange.max) || + ((x[i] + barWidth) < elemPtr->axes.x->axisRange.min)) { + continue; /* Abscissa is out of range of the + * x-axis */ + } + c1.x = x[i] - barOffset; + c1.y = y[i]; + c2.x = c1.x + barWidth; + c2.y = baseline; + + /* + * If the mode is "aligned" or "stacked" we need to adjust the x or y + * coordinates of the two corners. + */ + + if ((graphPtr->nBarGroups > 0) && (graphPtr->mode != BARS_INFRONT) && + (!graphPtr->stackAxes)) { + Blt_HashEntry *hPtr; + BarSetKey key; + + key.value = (float)x[i]; + key.axes = elemPtr->axes; + key.axes.y = NULL; + hPtr = Blt_FindHashEntry(&graphPtr->setTable, (char *)&key); + if (hPtr != NULL) { + Blt_HashTable *tablePtr; + const char *name; + + tablePtr = Blt_GetHashValue(hPtr); + name = (elemPtr->groupName != NULL) ? elemPtr->groupName : + elemPtr->axes.y->obj.name; + hPtr = Blt_FindHashEntry(tablePtr, name); + if (hPtr != NULL) { + BarGroup *groupPtr; + double slice, width, offset; + + groupPtr = Blt_GetHashValue(hPtr); + slice = barWidth / (double)graphPtr->maxBarSetSize; + offset = (slice * groupPtr->index); + if (graphPtr->maxBarSetSize > 1) { + offset += slice * 0.05; + slice *= 0.90; + } + switch (graphPtr->mode) { + case BARS_STACKED: + groupPtr->count++; + c2.y = groupPtr->lastY; + c1.y += c2.y; + groupPtr->lastY = c1.y; + c1.x += offset; + c2.x = c1.x + slice; + break; + + case BARS_ALIGNED: + slice /= groupPtr->nSegments; + c1.x += offset + (slice * groupPtr->count); + c2.x = c1.x + slice; + groupPtr->count++; + break; + + case BARS_OVERLAP: + slice /= (groupPtr->nSegments + 1); + width = slice + slice; + groupPtr->count++; + c1.x += offset + + (slice * (groupPtr->nSegments - groupPtr->count)); + c2.x = c1.x + width; + break; + + case BARS_INFRONT: + break; + } + } + } + } + invertBar = FALSE; + if (c1.y < c2.y) { + double temp; + + /* Handle negative bar values by swapping ordinates */ + temp = c1.y, c1.y = c2.y, c2.y = temp; + invertBar = TRUE; + } + /* + * Get the two corners of the bar segment and compute the rectangle + */ + ybot = c2.y; + c1 = Blt_Map2D(graphPtr, c1.x, c1.y, &elemPtr->axes); + c2 = Blt_Map2D(graphPtr, c2.x, c2.y, &elemPtr->axes); + if ((ybot == 0.0) && (elemPtr->axes.y->logScale)) { + c2.y = graphPtr->bottom; + } + + if (c2.y < c1.y) { + double t; + t = c1.y, c1.y = c2.y, c2.y = t; + } + if (c2.x < c1.x) { + double t; + t = c1.x, c1.x = c2.x, c2.x = t; + } + if ((c1.x > graphPtr->right) || (c2.x < graphPtr->left) || + (c1.y > graphPtr->bottom) || (c2.y < graphPtr->top)) { + continue; + } + /* Bound the bars horizontally by the width of the graph window */ + /* Bound the bars vertically by the position of the axis. */ + if (graphPtr->stackAxes) { + top = elemPtr->axes.y->screenMin; + bottom = elemPtr->axes.y->screenMin + elemPtr->axes.y->screenRange; + left = graphPtr->left; + right = graphPtr->right; + } else { + left = top = 0; + bottom = right = 10000; + /* Shouldn't really have a call to Tk_Width or Tk_Height in + * mapping routine. We only want to clamp the bar segment to the + * size of the window if we're actually mapped onscreen. */ + if (Tk_Height(graphPtr->tkwin) > 1) { + bottom = Tk_Height(graphPtr->tkwin); + } + if (Tk_Width(graphPtr->tkwin) > 1) { + right = Tk_Width(graphPtr->tkwin); + } + } + CLAMP(c1.y, top, bottom); + CLAMP(c2.y, top, bottom); + CLAMP(c1.x, left, right); + CLAMP(c2.x, left, right); + dx = FABS(c1.x - c2.x); + dy = FABS(c1.y - c2.y); + if ((dx == 0) || (dy == 0)) { + continue; + } + height = (int)dy; + if (invertBar) { + rp->y = (short int)MIN(c1.y, c2.y); + } else { + rp->y = (short int)(MAX(c1.y, c2.y)) - height; + } + rp->x = (short int)MIN(c1.x, c2.x); + rp->width = (short int)dx + 1; + rp->width |= 0x1; + if (rp->width < 1) { + rp->width = 1; + } + rp->height = height + 1; + if (rp->height < 1) { + rp->height = 1; + } + barToData[count] = i; /* Save the data index corresponding to + * the rectangle */ + count++; + rp++; + } + elemPtr->nBars = count; + elemPtr->bars = bars; + elemPtr->barToData = barToData; + if (elemPtr->nActiveIndices > 0) { + MapActiveBars(elemPtr); + } + + size = 20; + if (count > 0) { + size = bars->width; + } + { + Blt_ChainLink link; + + /* Set the symbol size of all the pen styles. */ + for (link = Blt_Chain_FirstLink(elemPtr->stylePalette); link != NULL; + link = Blt_Chain_NextLink(link)) { + BarStyle *stylePtr; + + stylePtr = Blt_Chain_GetValue(link); + stylePtr->symbolSize = size; + stylePtr->errorBarCapWidth = + (stylePtr->penPtr->errorBarCapWidth > 0) + ? stylePtr->penPtr->errorBarCapWidth : (size * 66666) / 100000; + stylePtr->errorBarCapWidth /= 2; + } + } + dataToStyle = (BarStyle **)Blt_StyleMap((Element *)elemPtr); + if (((elemPtr->yHigh.nValues > 0) && (elemPtr->yLow.nValues > 0)) || + ((elemPtr->xHigh.nValues > 0) && (elemPtr->xLow.nValues > 0)) || + (elemPtr->xError.nValues > 0) || (elemPtr->yError.nValues > 0)) { + MapErrorBars(graphPtr, elemPtr, dataToStyle); + } + MergePens(elemPtr, dataToStyle); + Blt_Free(dataToStyle); +} + +/* + *--------------------------------------------------------------------------- + * + * DrawSymbolProc -- + * + * Draw a symbol centered at the given x,y window coordinate based upon + * the element symbol type and size. + * + * Results: + * None. + * + * Problems: + * Most notable is the round-off errors generated when calculating the + * centered position of the symbol. + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static void +DrawSymbolProc(Graph *graphPtr, Drawable drawable, Element *basePtr, + int x, int y, int size) +{ + BarElement *elemPtr = (BarElement *)basePtr; + BarPen *penPtr; + int radius; + + penPtr = NORMALPEN(elemPtr); + if ((penPtr->fill == NULL) && (penPtr->outlineColor == NULL)) { + return; + } + radius = (size / 2); + size--; + + x -= radius; + y -= radius; + if (penPtr->fillGC != NULL) { + XSetTSOrigin(graphPtr->display, penPtr->fillGC, x, y); + } + if (penPtr->stipple != None) { + XFillRectangle(graphPtr->display, drawable, penPtr->fillGC, x, y, + size, size); + } else { + Blt_FillBackgroundRectangle(graphPtr->tkwin, drawable, penPtr->fill, + x, y, size, size, penPtr->borderWidth, penPtr->relief); + } + XDrawRectangle(graphPtr->display, drawable, penPtr->outlineGC, x, y, + size, size); + if (penPtr->fillGC != NULL) { + XSetTSOrigin(graphPtr->display, penPtr->fillGC, 0, 0); + } +} + +/* + *--------------------------------------------------------------------------- + * + * DrawBarSegments -- + * + * Draws each of the rectangular segments for the element. + * + * Results: + * None. + * + *--------------------------------------------------------------------------- + */ +static void +DrawBarSegments(Graph *graphPtr, Drawable drawable, BarPen *penPtr, + XRectangle *bars, int nBars) +{ + TkRegion rgn; + + { + XRectangle clip; + clip.x = graphPtr->left; + clip.y = graphPtr->top; + clip.width = graphPtr->right - graphPtr->left + 1; + clip.height = graphPtr->bottom - graphPtr->top + 1; + rgn = TkCreateRegion(); + TkUnionRectWithRegion(&clip, rgn, rgn); + } + if (penPtr->fill != NULL) { + XRectangle *rp, *rend; + int hasOutline; + int relief; + + relief = (penPtr->relief == TK_RELIEF_SOLID) + ? TK_RELIEF_FLAT: penPtr->relief; + hasOutline = ((relief == TK_RELIEF_FLAT) && + (penPtr->outlineColor != NULL)); + if (penPtr->stipple != None) { + TkSetRegion(graphPtr->display, penPtr->fillGC, rgn); + } + Blt_SetBackgroundClipRegion(graphPtr->tkwin, penPtr->fill, rgn); + if (hasOutline) { + TkSetRegion(graphPtr->display, penPtr->outlineGC, rgn); + } + for (rp = bars, rend = rp + nBars; rp < rend; rp++) { + if (penPtr->stipple != None) { + XFillRectangle(graphPtr->display, drawable, penPtr->fillGC, + rp->x, rp->y, rp->width, rp->height); + } else { + Blt_FillBackgroundRectangle(graphPtr->tkwin, drawable, + penPtr->fill, rp->x, rp->y, rp->width, rp->height, + penPtr->borderWidth, relief); + } + if (hasOutline) { + XDrawRectangle(graphPtr->display, drawable, penPtr->outlineGC, + rp->x, rp->y, rp->width, rp->height); + } + } + Blt_UnsetBackgroundClipRegion(graphPtr->tkwin, penPtr->fill); + if (hasOutline) { + XSetClipMask(graphPtr->display, penPtr->outlineGC, None); + } + if (penPtr->stipple != None) { + XSetClipMask(graphPtr->display, penPtr->fillGC, None); + } + } else if (penPtr->outlineColor != NULL) { + TkSetRegion(graphPtr->display, penPtr->outlineGC, rgn); + XDrawRectangles(graphPtr->display, drawable, penPtr->outlineGC, bars, + nBars); + XSetClipMask(graphPtr->display, penPtr->outlineGC, None); + } + TkDestroyRegion(rgn); +} + +/* + *--------------------------------------------------------------------------- + * + * DrawBarValues -- + * + * Draws the numeric value of the bar. + * + * Results: + * None. + * + *--------------------------------------------------------------------------- + */ +static void +DrawBarValues(Graph *graphPtr, Drawable drawable, BarElement *elemPtr, + BarPen *penPtr, XRectangle *bars, int nBars, int *barToData) +{ + XRectangle *rp, *rend; + int count; + const char *fmt; + + fmt = penPtr->valueFormat; + if (fmt == NULL) { + fmt = "%g"; + } + count = 0; + for (rp = bars, rend = rp + nBars; rp < rend; rp++) { + Point2d anchorPos; + double x, y; + char string[TCL_DOUBLE_SPACE * 2 + 2]; + + x = elemPtr->x.values[barToData[count]]; + y = elemPtr->y.values[barToData[count]]; + + count++; + if (penPtr->valueShow == SHOW_X) { + sprintf_s(string, TCL_DOUBLE_SPACE, fmt, x); + } else if (penPtr->valueShow == SHOW_Y) { + sprintf_s(string, TCL_DOUBLE_SPACE, fmt, y); + } else if (penPtr->valueShow == SHOW_BOTH) { + sprintf_s(string, TCL_DOUBLE_SPACE, fmt, x); + strcat(string, ","); + sprintf_s(string + strlen(string), TCL_DOUBLE_SPACE, fmt, y); + } + if (graphPtr->inverted) { + anchorPos.y = rp->y + rp->height * 0.5; + anchorPos.x = rp->x + rp->width; + if (x < graphPtr->baseline) { + anchorPos.x -= rp->width; + } + } else { + anchorPos.x = rp->x + rp->width * 0.5; + anchorPos.y = rp->y; + if (y < graphPtr->baseline) { + anchorPos.y += rp->height; + } + } + Blt_DrawText(graphPtr->tkwin, drawable, string, &penPtr->valueStyle, + (int)anchorPos.x, (int)anchorPos.y); + } +} + + +/* + *--------------------------------------------------------------------------- + * + * DrawNormalBar -- + * + * Draws the rectangle representing the bar element. If the relief + * option is set to "raised" or "sunken" and the bar borderwidth is set + * (borderwidth > 0), a 3D border is drawn around the bar. + * + * Don't draw bars that aren't visible (i.e. within the limits of the + * axis). + * + * Results: + * None. + * + * Side effects: + * X drawing commands are output. + * + *--------------------------------------------------------------------------- + */ +static void +DrawNormalBarProc(Graph *graphPtr, Drawable drawable, Element *basePtr) +{ + BarElement *elemPtr = (BarElement *)basePtr; + int count; + Blt_ChainLink link; + + count = 0; + for (link = Blt_Chain_FirstLink(elemPtr->stylePalette); link != NULL; + link = Blt_Chain_NextLink(link)) { + BarStyle *stylePtr; + BarPen *penPtr; + + stylePtr = Blt_Chain_GetValue(link); + penPtr = stylePtr->penPtr; + if (stylePtr->nBars > 0) { + DrawBarSegments(graphPtr, drawable, penPtr, stylePtr->bars, + stylePtr->nBars); + } + if ((stylePtr->xeb.length > 0) && (penPtr->errorBarShow & SHOW_X)) { + Blt_Draw2DSegments(graphPtr->display, drawable, penPtr->errorBarGC, + stylePtr->xeb.segments, stylePtr->xeb.length); + } + if ((stylePtr->yeb.length > 0) && (penPtr->errorBarShow & SHOW_Y)) { + Blt_Draw2DSegments(graphPtr->display, drawable, penPtr->errorBarGC, + stylePtr->yeb.segments, stylePtr->yeb.length); + } + if (penPtr->valueShow != SHOW_NONE) { + DrawBarValues(graphPtr, drawable, elemPtr, penPtr, + stylePtr->bars, stylePtr->nBars, + elemPtr->barToData + count); + } + count += stylePtr->nBars; + } +} + +/* + *--------------------------------------------------------------------------- + * + * DrawActiveBar -- + * + * Draws bars representing the active segments of the bar element. If + * the -relief option is set (other than "flat") and the borderwidth is + * greater than 0, a 3D border is drawn around the each bar segment. + * + * Results: + * None. + * + * Side effects: + * X drawing commands are output. + * + *--------------------------------------------------------------------------- + */ +static void +DrawActiveBarProc(Graph *graphPtr, Drawable drawable, Element *basePtr) +{ + BarElement *elemPtr = (BarElement *)basePtr; + + if (elemPtr->activePenPtr != NULL) { + BarPen *penPtr = elemPtr->activePenPtr; + + if (elemPtr->nActiveIndices > 0) { + if (elemPtr->flags & ACTIVE_PENDING) { + MapActiveBars(elemPtr); + } + DrawBarSegments(graphPtr, drawable, penPtr, elemPtr->activeRects, + elemPtr->nActive); + if (penPtr->valueShow != SHOW_NONE) { + DrawBarValues(graphPtr, drawable, elemPtr, penPtr, + elemPtr->activeRects, elemPtr->nActive, + elemPtr->activeToData); + } + } else if (elemPtr->nActiveIndices < 0) { + DrawBarSegments(graphPtr, drawable, penPtr, elemPtr->bars, + elemPtr->nBars); + if (penPtr->valueShow != SHOW_NONE) { + DrawBarValues(graphPtr, drawable, elemPtr, penPtr, + elemPtr->bars, elemPtr->nBars, elemPtr->barToData); + } + } + } +} + +/* + *--------------------------------------------------------------------------- + * + * SymbolToPostScript -- + * + * Draw a symbol centered at the given x,y window coordinate based upon + * the element symbol type and size. + * + * Results: + * None. + * + * Problems: + * Most notable is the round-off errors generated when calculating the + * centered position of the symbol. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static void +SymbolToPostScriptProc( + Graph *graphPtr, + Blt_Ps ps, + Element *basePtr, + double x, double y, + int size) +{ + BarElement *elemPtr = (BarElement *)basePtr; + BarPen *penPtr; + + penPtr = NORMALPEN(elemPtr); + if ((penPtr->fill == NULL) && (penPtr->outlineColor == NULL)) { + return; + } + /* + * Build a PostScript procedure to draw the fill and outline of the symbol + * after the path of the symbol shape has been formed + */ + Blt_Ps_Append(ps, "\n" + "/DrawSymbolProc {\n" + "gsave\n "); + if (penPtr->stipple != None) { + if (penPtr->fill != NULL) { + Blt_Ps_XSetBackground(ps, Blt_BackgroundBorderColor(penPtr->fill)); + Blt_Ps_Append(ps, " gsave fill grestore\n "); + } + if (penPtr->outlineColor != NULL) { + Blt_Ps_XSetForeground(ps, penPtr->outlineColor); + } else { + Blt_Ps_XSetForeground(ps, Blt_BackgroundBorderColor(penPtr->fill)); + } + Blt_Ps_XSetStipple(ps, graphPtr->display, penPtr->stipple); + } else if (penPtr->outlineColor != NULL) { + Blt_Ps_XSetForeground(ps, penPtr->outlineColor); + Blt_Ps_Append(ps, " fill\n"); + } + Blt_Ps_Append(ps, " grestore\n"); + Blt_Ps_Append(ps, "} def\n\n"); + Blt_Ps_Format(ps, "%g %g %d Sq\n", x, y, size); +} + +static void +SegmentsToPostScript(Graph *graphPtr, Blt_Ps ps, BarPen *penPtr, + XRectangle *bars, int nBars) +{ + XRectangle *rp, *rend; + + if ((penPtr->fill == NULL) && (penPtr->outlineColor == NULL)) { + return; + } + for (rp = bars, rend = rp + nBars; rp < rend; rp++) { + if ((rp->width < 1) || (rp->height < 1)) { + continue; + } + if (penPtr->stipple != None) { + Blt_Ps_Rectangle(ps, rp->x, rp->y, rp->width - 1, rp->height - 1); + if (penPtr->fill != NULL) { + Blt_Ps_XSetBackground(ps,Blt_BackgroundBorderColor(penPtr->fill)); + Blt_Ps_Append(ps, "gsave fill grestore\n"); + } + if (penPtr->outlineColor != NULL) { + Blt_Ps_XSetForeground(ps, penPtr->outlineColor); + } else { + Blt_Ps_XSetForeground(ps,Blt_BackgroundBorderColor(penPtr->fill)); + } + Blt_Ps_XSetStipple(ps, graphPtr->display, penPtr->stipple); + } else if (penPtr->outlineColor != NULL) { + Blt_Ps_XSetForeground(ps, penPtr->outlineColor); + Blt_Ps_XFillRectangle(ps, (double)rp->x, (double)rp->y, + (int)rp->width - 1, (int)rp->height - 1); + } + if ((penPtr->fill != NULL) && (penPtr->borderWidth > 0) && + (penPtr->relief != TK_RELIEF_FLAT)) { + Blt_Ps_Draw3DRectangle(ps, Blt_BackgroundBorder(penPtr->fill), + (double)rp->x, (double)rp->y, (int)rp->width, (int)rp->height, + penPtr->borderWidth, penPtr->relief); + } + } +} + +static void +BarValuesToPostScript(Graph *graphPtr, Blt_Ps ps, BarElement *elemPtr, + BarPen *penPtr, XRectangle *bars, int nBars, + int *barToData) +{ + XRectangle *rp, *rend; + int count; + const char *fmt; + char string[TCL_DOUBLE_SPACE * 2 + 2]; + double x, y; + Point2d anchorPos; + + count = 0; + fmt = penPtr->valueFormat; + if (fmt == NULL) { + fmt = "%g"; + } + for (rp = bars, rend = rp + nBars; rp < rend; rp++) { + x = elemPtr->x.values[barToData[count]]; + y = elemPtr->y.values[barToData[count]]; + count++; + if (penPtr->valueShow == SHOW_X) { + sprintf_s(string, TCL_DOUBLE_SPACE, fmt, x); + } else if (penPtr->valueShow == SHOW_Y) { + sprintf_s(string, TCL_DOUBLE_SPACE, fmt, y); + } else if (penPtr->valueShow == SHOW_BOTH) { + sprintf_s(string, TCL_DOUBLE_SPACE, fmt, x); + strcat(string, ","); + sprintf_s(string + strlen(string), TCL_DOUBLE_SPACE, fmt, y); + } + if (graphPtr->inverted) { + anchorPos.y = rp->y + rp->height * 0.5; + anchorPos.x = rp->x + rp->width; + if (x < graphPtr->baseline) { + anchorPos.x -= rp->width; + } + } else { + anchorPos.x = rp->x + rp->width * 0.5; + anchorPos.y = rp->y; + if (y < graphPtr->baseline) { + anchorPos.y += rp->height; + } + } + Blt_Ps_DrawText(ps, string, &penPtr->valueStyle, anchorPos.x, + anchorPos.y); + } +} + +/* + *--------------------------------------------------------------------------- + * + * ActiveBarToPostScript -- + * + * Similar to the NormalBarToPostScript procedure, generates PostScript + * commands to display the bars representing the active bar segments of + * the element. + * + * Results: + * None. + * + * Side effects: + * PostScript pen width, dashes, and color settings are changed. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static void +ActiveBarToPostScriptProc(Graph *graphPtr, Blt_Ps ps, Element *basePtr) +{ + BarElement *elemPtr = (BarElement *)basePtr; + + if (elemPtr->activePenPtr != NULL) { + BarPen *penPtr = elemPtr->activePenPtr; + + if (elemPtr->nActiveIndices > 0) { + if (elemPtr->flags & ACTIVE_PENDING) { + MapActiveBars(elemPtr); + } + SegmentsToPostScript(graphPtr, ps, penPtr, elemPtr->activeRects, + elemPtr->nActive); + if (penPtr->valueShow != SHOW_NONE) { + BarValuesToPostScript(graphPtr, ps, elemPtr, penPtr, + elemPtr->activeRects, elemPtr->nActive, elemPtr->activeToData); + } + } else if (elemPtr->nActiveIndices < 0) { + SegmentsToPostScript(graphPtr, ps, penPtr, elemPtr->bars, + elemPtr->nBars); + if (penPtr->valueShow != SHOW_NONE) { + BarValuesToPostScript(graphPtr, ps, elemPtr, penPtr, + elemPtr->bars, elemPtr->nBars, elemPtr->barToData); + } + } + } +} + +/* + *--------------------------------------------------------------------------- + * + * NormalBarToPostScript -- + * + * Generates PostScript commands to form the bars representing the + * segments of the bar element. + * + * Results: + * None. + * + * Side effects: + * PostScript pen width, dashes, and color settings are changed. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static void +NormalBarToPostScriptProc(Graph *graphPtr, Blt_Ps ps, Element *basePtr) +{ + BarElement *elemPtr = (BarElement *)basePtr; + Blt_ChainLink link; + int count; + + count = 0; + for (link = Blt_Chain_FirstLink(elemPtr->stylePalette); link != NULL; + link = Blt_Chain_NextLink(link)) { + BarStyle *stylePtr; + BarPen *penPtr; + XColor *colorPtr; + + stylePtr = Blt_Chain_GetValue(link); + penPtr = stylePtr->penPtr; + if (stylePtr->nBars > 0) { + SegmentsToPostScript(graphPtr, ps, penPtr, stylePtr->bars, + stylePtr->nBars); + } + colorPtr = penPtr->errorBarColor; + if (colorPtr == COLOR_DEFAULT) { + colorPtr = penPtr->outlineColor; + } + if ((stylePtr->xeb.length > 0) && (penPtr->errorBarShow & SHOW_X)) { + Blt_Ps_XSetLineAttributes(ps, colorPtr, penPtr->errorBarLineWidth, + NULL, CapButt, JoinMiter); + Blt_Ps_Draw2DSegments(ps, stylePtr->xeb.segments, + stylePtr->xeb.length); + } + if ((stylePtr->yeb.length > 0) && (penPtr->errorBarShow & SHOW_Y)) { + Blt_Ps_XSetLineAttributes(ps, colorPtr, penPtr->errorBarLineWidth, + NULL, CapButt, JoinMiter); + Blt_Ps_Draw2DSegments(ps, stylePtr->yeb.segments, + stylePtr->yeb.length); + } + if (penPtr->valueShow != SHOW_NONE) { + BarValuesToPostScript(graphPtr, ps, elemPtr, penPtr, + stylePtr->bars, stylePtr->nBars, elemPtr->barToData + count); + } + count += stylePtr->nBars; + } +} + +/* + *--------------------------------------------------------------------------- + * + * DestroyBar -- + * + * Release memory and resources allocated for the bar element. + * + * Results: + * None. + * + * Side effects: + * Everything associated with the bar element is freed up. + * + *--------------------------------------------------------------------------- + */ + +static void +DestroyBarProc(Graph *graphPtr, Element *basePtr) +{ + BarElement *elemPtr = (BarElement *)basePtr; + + DestroyBarPen(graphPtr, elemPtr->builtinPenPtr); + if (elemPtr->activePenPtr != NULL) { + Blt_FreePen((Pen *)elemPtr->activePenPtr); + } + ResetBar(elemPtr); + if (elemPtr->stylePalette != NULL) { + Blt_FreeStylePalette(elemPtr->stylePalette); + Blt_Chain_Destroy(elemPtr->stylePalette); + } + if (elemPtr->activeIndices != NULL) { + Blt_Free(elemPtr->activeIndices); + } +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_BarElement -- + * + * Allocate memory and initialize methods for the new bar element. + * + * Results: + * The pointer to the newly allocated element structure is returned. + * + * Side effects: + * Memory is allocated for the bar element structure. + * + *--------------------------------------------------------------------------- + */ + +static ElementProcs barProcs = { + ClosestBarProc, + ConfigureBarProc, + DestroyBarProc, + DrawActiveBarProc, + DrawNormalBarProc, + DrawSymbolProc, + GetBarExtentsProc, + ActiveBarToPostScriptProc, + NormalBarToPostScriptProc, + SymbolToPostScriptProc, + MapBarProc, +}; + + +Element * +Blt_BarElement(Graph *graphPtr, const char *name, ClassId classId) +{ + BarElement *elemPtr; + + elemPtr = Blt_AssertCalloc(1, sizeof(BarElement)); + elemPtr->procsPtr = &barProcs; + elemPtr->configSpecs = barElemConfigSpecs; + elemPtr->legendRelief = TK_RELIEF_FLAT; + Blt_GraphSetObjectClass(&elemPtr->obj, classId); + elemPtr->obj.name = Blt_AssertStrdup(name); + elemPtr->obj.graphPtr = graphPtr; + /* By default, an element's name and label are the same. */ + elemPtr->label = Blt_AssertStrdup(name); + elemPtr->builtinPenPtr = &elemPtr->builtinPen; + InitializeBarPen(elemPtr->builtinPenPtr); + elemPtr->stylePalette = Blt_Chain_Create(); + bltBarStylesOption.clientData = (ClientData)sizeof(BarStyle); + return (Element *)elemPtr; +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_InitBarSetTable -- + * + * Generate a table of abscissa frequencies. Duplicate x-coordinates + * (depending upon the bar drawing mode) indicate that something special + * should be done with each bar segment mapped to the same abscissa + * (i.e. it should be stacked, aligned, or overlay-ed with other segments) + * + * Results: + * None. + * + * Side effects: + * Memory is allocated for the bar element structure. + * + *--------------------------------------------------------------------------- + */ +void +Blt_InitBarSetTable(Graph *graphPtr) +{ + Blt_ChainLink link; + int nStacks, nSegs; + Blt_HashTable setTable; + int sum, max; + Blt_HashEntry *hPtr; + Blt_HashSearch iter; + + /* + * Free resources associated with a previous frequency table. This includes + * the array of frequency information and the table itself + */ + Blt_DestroyBarSets(graphPtr); + if (graphPtr->mode == BARS_INFRONT) { + return; /* No set table is needed for + * "infront" mode */ + } + Blt_InitHashTable(&graphPtr->setTable, sizeof(BarSetKey) / sizeof(int)); + + /* + * Initialize a hash table and fill it with unique abscissas. Keep track + * of the frequency of each x-coordinate and how many abscissas have + * duplicate mappings. + */ + Blt_InitHashTable(&setTable, sizeof(BarSetKey) / sizeof(int)); + nSegs = nStacks = 0; + for (link = Blt_Chain_FirstLink(graphPtr->elements.displayList); + link != NULL; link = Blt_Chain_NextLink(link)) { + BarElement *elemPtr; + double *x, *xend; + int nPoints; + + elemPtr = Blt_Chain_GetValue(link); + if ((elemPtr->flags & HIDE) || (elemPtr->obj.classId != CID_ELEM_BAR)) { + continue; + } + nSegs++; + nPoints = NUMBEROFPOINTS(elemPtr); + for (x = elemPtr->x.values, xend = x + nPoints; x < xend; x++) { + Blt_HashEntry *hPtr; + Blt_HashTable *tablePtr; + BarSetKey key; + int isNew; + size_t count; + const char *name; + + key.value = *x; + key.axes = elemPtr->axes; + key.axes.y = NULL; + hPtr = Blt_CreateHashEntry(&setTable, (char *)&key, &isNew); + if (isNew) { + tablePtr = Blt_AssertMalloc(sizeof(Blt_HashTable)); + Blt_InitHashTable(tablePtr, BLT_STRING_KEYS); + Blt_SetHashValue(hPtr, tablePtr); + } else { + tablePtr = Blt_GetHashValue(hPtr); + } + name = (elemPtr->groupName != NULL) ? elemPtr->groupName : + elemPtr->axes.y->obj.name; + hPtr = Blt_CreateHashEntry(tablePtr, name, &isNew); + if (isNew) { + count = 1; + } else { + count = (size_t)Blt_GetHashValue(hPtr); + count++; + } + Blt_SetHashValue(hPtr, (ClientData)count); + } + } + if (setTable.numEntries == 0) { + return; /* No bar elements to be displayed */ + } + sum = max = 0; + for (hPtr = Blt_FirstHashEntry(&setTable, &iter); hPtr != NULL; + hPtr = Blt_NextHashEntry(&iter)) { + Blt_HashTable *tablePtr; + Blt_HashEntry *hPtr2; + BarSetKey *keyPtr; + int isNew; + + keyPtr = (BarSetKey *)Blt_GetHashKey(&setTable, hPtr); + hPtr2 = Blt_CreateHashEntry(&graphPtr->setTable, (char *)keyPtr,&isNew); + tablePtr = Blt_GetHashValue(hPtr); + Blt_SetHashValue(hPtr2, tablePtr); + if (max < tablePtr->numEntries) { + max = tablePtr->numEntries; /* # of stacks in group. */ + } + sum += tablePtr->numEntries; + } + Blt_DeleteHashTable(&setTable); + if (sum > 0) { + BarGroup *groupPtr; + Blt_HashEntry *hPtr; + Blt_HashSearch iter; + + graphPtr->barGroups = Blt_AssertCalloc(sum, sizeof(BarGroup)); + groupPtr = graphPtr->barGroups; + for (hPtr = Blt_FirstHashEntry(&graphPtr->setTable, &iter); + hPtr != NULL; hPtr = Blt_NextHashEntry(&iter)) { + BarSetKey *keyPtr; + Blt_HashTable *tablePtr; + Blt_HashEntry *hPtr2; + Blt_HashSearch iter2; + size_t xcount; + + tablePtr = Blt_GetHashValue(hPtr); + keyPtr = (BarSetKey *)Blt_GetHashKey(&setTable, hPtr); + xcount = 0; + for (hPtr2 = Blt_FirstHashEntry(tablePtr, &iter2); hPtr2!=NULL; + hPtr2 = Blt_NextHashEntry(&iter2)) { + size_t count; + + count = (size_t)Blt_GetHashValue(hPtr2); + groupPtr->nSegments = count; + groupPtr->axes = keyPtr->axes; + Blt_SetHashValue(hPtr2, groupPtr); + groupPtr->index = xcount++; + groupPtr++; + } + } + } + graphPtr->maxBarSetSize = max; + graphPtr->nBarGroups = sum; +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_ComputeStacks -- + * + * Determine the height of each stack of bar segments. A stack is created + * by designating two or more points with the same abscissa. Each ordinate + * defines the height of a segment in the stack. This procedure simply + * looks at all the data points summing the heights of each stacked + * segment. The sum is saved in the frequency information table. This + * value will be used to calculate the y-axis limits (data limits aren't + * sufficient). + * + * Results: + * None. + * + * Side effects: + * The heights of each stack is computed. CheckBarGroups will use this + * information to adjust the y-axis limits if necessary. + * + *--------------------------------------------------------------------------- + */ +void +Blt_ComputeBarStacks(Graph *graphPtr) +{ + Blt_ChainLink link; + + if ((graphPtr->mode != BARS_STACKED) || (graphPtr->nBarGroups == 0)) { + return; + } + + /* Initialize the stack sums to zero. */ + { + BarGroup *gp, *gend; + + for (gp = graphPtr->barGroups, gend = gp + graphPtr->nBarGroups; + gp < gend; gp++) { + gp->sum = 0.0; + } + } + + /* Consider each bar x-y coordinate. Add the ordinates of duplicate + * abscissas. */ + + for (link = Blt_Chain_FirstLink(graphPtr->elements.displayList); + link != NULL; link = Blt_Chain_NextLink(link)) { + BarElement *elemPtr; + double *x, *y, *xend; + + elemPtr = Blt_Chain_GetValue(link); + if ((elemPtr->flags & HIDE) || (elemPtr->obj.classId != CID_ELEM_BAR)) { + continue; + } + for (x = elemPtr->x.values, y = elemPtr->y.values, + xend = x + NUMBEROFPOINTS(elemPtr); x < xend; x++, y++) { + BarSetKey key; + BarGroup *groupPtr; + Blt_HashEntry *hPtr; + Blt_HashTable *tablePtr; + const char *name; + + key.value = *x; + key.axes = elemPtr->axes; + key.axes.y = NULL; + hPtr = Blt_FindHashEntry(&graphPtr->setTable, (char *)&key); + if (hPtr == NULL) { + continue; + } + tablePtr = Blt_GetHashValue(hPtr); + name = (elemPtr->groupName != NULL) ? elemPtr->groupName : + elemPtr->axes.y->obj.name; + hPtr = Blt_FindHashEntry(tablePtr, name); + if (hPtr == NULL) { + continue; + } + groupPtr = Blt_GetHashValue(hPtr); + groupPtr->sum += *y; + } + } +} + +void +Blt_ResetBarGroups(Graph *graphPtr) +{ + BarGroup *gp, *gend; + + for (gp = graphPtr->barGroups, gend = gp + graphPtr->nBarGroups; gp < gend; + gp++) { + gp->lastY = 0.0; + gp->count = 0; + } +} + +void +Blt_DestroyBarSets(Graph *graphPtr) +{ + Blt_HashSearch iter; + Blt_HashEntry *hPtr; + + if (graphPtr->barGroups != NULL) { + Blt_Free(graphPtr->barGroups); + graphPtr->barGroups = NULL; + } + graphPtr->nBarGroups = 0; + for (hPtr = Blt_FirstHashEntry(&graphPtr->setTable, &iter); + hPtr != NULL; hPtr = Blt_NextHashEntry(&iter)) { + Blt_HashTable *tablePtr; + + tablePtr = Blt_GetHashValue(hPtr); + Blt_DeleteHashTable(tablePtr); + Blt_Free(tablePtr); + } + Blt_DeleteHashTable(&graphPtr->setTable); + Blt_InitHashTable(&graphPtr->setTable, sizeof(BarSetKey) / sizeof(int)); +} diff --git a/src/bltGrElemLine.C b/src/bltGrElemLine.C new file mode 100644 index 0000000..82234da --- /dev/null +++ b/src/bltGrElemLine.C @@ -0,0 +1,5347 @@ + +/* + * bltGrLine.c -- + * + * This module implements line graph and stripchart elements for the BLT graph + * widget. + * + * Copyright (c) 1993 George A Howlett. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include "bltGraph.h" +#include "bltChain.h" +#include <X11/Xutil.h> +#include "bltGrElem.h" +#include "tkDisplay.h" +#include "bltBgStyle.h" +#include "bltImage.h" +#include "bltBitmap.h" + +#define COLOR_DEFAULT (XColor *)1 +#define PATTERN_SOLID ((Pixmap)1) + +#define PEN_INCREASING 1 /* Draw line segments for only those + * data points whose abscissas are + * monotonically increasing in + * order. */ +#define PEN_DECREASING 2 /* Lines will be drawn between only + * those points whose abscissas are + * decreasing in order. */ + +#define PEN_BOTH_DIRECTIONS (PEN_INCREASING | PEN_DECREASING) + +/* Lines will be drawn between points regardless of the ordering of the + * abscissas */ + +#define BROKEN_TRACE(dir,last,next) \ + (((((dir) & PEN_DECREASING) == 0) && ((next) < (last))) || \ + ((((dir) & PEN_INCREASING) == 0) && ((next) > (last)))) + +#define DRAW_SYMBOL(linePtr) \ + (((linePtr)->symbolCounter % (linePtr)->symbolInterval) == 0) + +typedef enum { + PEN_SMOOTH_LINEAR, /* Line segments */ + PEN_SMOOTH_STEP, /* Step-and-hold */ + PEN_SMOOTH_NATURAL, /* Natural cubic spline */ + PEN_SMOOTH_QUADRATIC, /* Quadratic spline */ + PEN_SMOOTH_CATROM, /* Catrom parametric spline */ + PEN_SMOOTH_LAST /* Sentinel */ +} Smoothing; + +typedef struct { + const char *name; + Smoothing value; +} SmoothingInfo; + +static SmoothingInfo smoothingInfo[] = { + { "none", PEN_SMOOTH_LINEAR }, + { "linear", PEN_SMOOTH_LINEAR }, + { "step", PEN_SMOOTH_STEP }, + { "natural", PEN_SMOOTH_NATURAL }, + { "cubic", PEN_SMOOTH_NATURAL }, + { "quadratic", PEN_SMOOTH_QUADRATIC }, + { "catrom", PEN_SMOOTH_CATROM }, + { (char *)NULL, PEN_SMOOTH_LAST } +}; + + +typedef struct { + Point2d *screenPts; /* Array of transformed coordinates */ + int nScreenPts; /* Number of coordinates */ + int *styleMap; /* Index of pen styles */ + int *map; /* Maps segments/traces to data + * points */ +} MapInfo; + +/* Symbol types for line elements */ +typedef enum { + SYMBOL_NONE, + SYMBOL_SQUARE, + SYMBOL_CIRCLE, + SYMBOL_DIAMOND, + SYMBOL_PLUS, + SYMBOL_CROSS, + SYMBOL_SPLUS, + SYMBOL_SCROSS, + SYMBOL_TRIANGLE, + SYMBOL_ARROW, + SYMBOL_BITMAP, + SYMBOL_IMAGE +} SymbolType; + +typedef struct { + const char *name; + unsigned int minChars; + SymbolType type; +} GraphSymbolType; + +static GraphSymbolType graphSymbols[] = { + { "arrow", 1, SYMBOL_ARROW, }, + { "circle", 2, SYMBOL_CIRCLE, }, + { "cross", 2, SYMBOL_CROSS, }, + { "diamond", 1, SYMBOL_DIAMOND, }, + { "none", 1, SYMBOL_NONE, }, + { "plus", 1, SYMBOL_PLUS, }, + { "scross", 2, SYMBOL_SCROSS, }, + { "splus", 2, SYMBOL_SPLUS, }, + { "square", 2, SYMBOL_SQUARE, }, + { "triangle", 1, SYMBOL_TRIANGLE, }, + { NULL, 0, 0 }, +}; + +typedef struct { + SymbolType type; /* Type of symbol to be drawn/printed */ + + int size; /* Requested size of symbol in pixels */ + + XColor *outlineColor; /* Outline color */ + + int outlineWidth; /* Width of the outline */ + + GC outlineGC; /* Outline graphics context */ + + XColor *fillColor; /* Normal fill color */ + + GC fillGC; /* Fill graphics context */ + + Tk_Image image; /* This is used of image symbols. */ + + /* The last two fields are used only for bitmap symbols. */ + + Pixmap bitmap; /* Bitmap to determine + * foreground/background pixels of the + * symbol */ + Pixmap mask; /* Bitmap representing the transparent + * pixels of the symbol */ +} Symbol; + +typedef struct { + int start; /* Index into the X-Y coordinate arrays + * indicating where trace starts. */ + GraphPoints screenPts; /* Array of screen coordinates + * (malloc-ed) representing the + * trace. */ +} Trace; + +typedef struct { + const char *name; /* Pen style identifier. If NULL pen + * was statically allocated. */ + ClassId classId; /* Type of pen */ + const char *typeId; /* String token identifying the type of + * pen */ + unsigned int flags; /* Indicates if the pen element is + * active or normal */ + int refCount; /* Reference count for elements using + * this pen. */ + Blt_HashEntry *hashPtr; + + Blt_ConfigSpec *configSpecs; /* Configuration specifications */ + + PenConfigureProc *configProc; + PenDestroyProc *destroyProc; + Graph *graphPtr; /* Graph that the pen is associated + * with. */ + + /* Symbol attributes. */ + Symbol symbol; /* Element symbol type */ + + /* Trace attributes. */ + int traceWidth; /* Width of the line segments. If + * lineWidth is 0, no line will be + * drawn, only symbols. */ + + Blt_Dashes traceDashes; /* Dash on-off list value */ + + XColor *traceColor; /* Line segment color */ + + XColor *traceOffColor; /* Line segment dash gap color */ + + GC traceGC; /* Line segment graphics context */ + + /* Error bar attributes. */ + int errorBarShow; /* Describes which error bars to display: + * none, x, y, or * both. */ + + int errorBarLineWidth; /* Width of the error bar segments. */ + + int errorBarCapWidth; /* Width of the cap on error bars. */ + + XColor *errorBarColor; /* Color of the error bar. */ + + GC errorBarGC; /* Error bar graphics context. */ + + /* Show value attributes. */ + int valueShow; /* Indicates whether to display data + * value. Values are x, y, both, or + * none. */ + const char *valueFormat; /* A printf format string. */ + + TextStyle valueStyle; /* Text attributes (color, font, + * rotation, etc.) of the value. */ +} LinePen; + +typedef struct { + Weight weight; /* Weight range where this pen is + * valid. */ + LinePen *penPtr; /* Pen to use. */ + GraphPoints symbolPts; + + GraphSegments lines; /* Points to start of the line segments + * for this pen. */ + GraphSegments xeb, yeb; /* X and Y axis error bars. */ + + int symbolSize; /* Size of the pen's symbol scaled to + * the current graph size. */ + int errorBarCapWidth; /* Length of the cap ends on each error + * bar. */ +} LineStyle; + +typedef struct { + GraphObj obj; /* Must be first field in element. */ + unsigned int flags; + Blt_HashEntry *hashPtr; + + /* Fields specific to elements. */ + const char *label; /* Label displayed in legend */ + unsigned short row, col; /* Position of the entry in the + * legend. */ + int legendRelief; /* Relief of label in legend. */ + Axis2d axes; /* X-axis and Y-axis mapping the + * element */ + ElemValues x, y, w; /* Contains array of floating point + * graph coordinate values. Also holds + * min/max * and the number of + * coordinates */ + int *activeIndices; /* Array of indices (malloc-ed) which + * indicate which data points are active + * (drawn * with "active" colors). */ + int nActiveIndices; /* Number of active data points. + * Special case: if nActiveIndices < 0 + * and the * active bit is set in + * "flags", then all data points are + * drawn active. */ + ElementProcs *procsPtr; + Blt_ConfigSpec *configSpecs; /* Configuration specifications. */ + LinePen *activePenPtr; /* Standard Pens */ + LinePen *normalPenPtr; + LinePen *builtinPenPtr; + Blt_Chain styles; /* Palette of pens. */ + + /* Symbol scaling */ + int scaleSymbols; /* If non-zero, the symbols will scale + * in size as the graph is zoomed + * in/out. */ + + double xRange, yRange; /* Initial X-axis and Y-axis ranges: + * used to scale the size of element's + * symbol. */ + int state; + Blt_ChainLink link; /* Element's link in display list. */ + + /* The line element specific fields start here. */ + + ElemValues xError; /* Relative/symmetric X error values. */ + ElemValues yError; /* Relative/symmetric Y error values. */ + ElemValues xHigh, xLow; /* Absolute/asymmetric X-coordinate + * high/low error values. */ + ElemValues yHigh, yLow; /* Absolute/asymmetric Y-coordinate + * high/low error values. */ + LinePen builtinPen; + int errorBarCapWidth; /* Length of cap on error bars */ + + /* Line smoothing */ + Smoothing reqSmooth; /* Requested smoothing function to use + * for connecting the data points */ + Smoothing smooth; /* Smoothing function used. */ + float rTolerance; /* Tolerance to reduce the number of + * points displayed. */ + + /* Drawing-related data structures. */ + + /* Area-under-curve fill attributes. */ + XColor *fillFgColor; + XColor *fillBgColor; + GC fillGC; + + Blt_Background fillBg; /* Background for fill area. */ + + Point2d *fillPts; /* Array of points used to draw polygon + * to fill area under the curve */ + int nFillPts; + + /* Symbol points */ + GraphPoints symbolPts; + + /* Active symbol points */ + GraphPoints activePts; + GraphSegments xeb, yeb; /* Point to start of this pen's X-error + * bar segments in the element's + * array. */ + int reqMaxSymbols; + int symbolInterval; + int symbolCounter; + + /* X-Y graph-specific fields */ + + int penDir; /* Indicates if a change in the pen + * direction should be considered a + * retrace (line segment is not + * drawn). */ + Blt_Chain traces; /* List of traces (a trace is a series + * of contiguous line segments). New + * traces are generated when either + * the next segment changes the pen + * direction, or the end point is + * clipped by the plotting area. */ + + /* Stripchart-specific fields */ + + GraphSegments lines; /* Holds the the line segments of the + * element trace. The segments are + * grouped by pen style. */ +} LineElement; + +static Blt_OptionParseProc ObjToSmoothProc; +static Blt_OptionPrintProc SmoothToObjProc; +static Blt_CustomOption smoothOption = +{ + ObjToSmoothProc, SmoothToObjProc, NULL, (ClientData)0 +}; + +static Blt_OptionParseProc ObjToPenDirProc; +static Blt_OptionPrintProc PenDirToObjProc; +static Blt_CustomOption penDirOption = +{ + ObjToPenDirProc, PenDirToObjProc, NULL, (ClientData)0 +}; + +static Blt_OptionFreeProc FreeSymbolProc; +static Blt_OptionParseProc ObjToSymbolProc; +static Blt_OptionPrintProc SymbolToObjProc; +static Blt_CustomOption symbolOption = +{ + ObjToSymbolProc, SymbolToObjProc, FreeSymbolProc, (ClientData)0 +}; + +BLT_EXTERN Blt_CustomOption bltLineStylesOption; +BLT_EXTERN Blt_CustomOption bltColorOption; +BLT_EXTERN Blt_CustomOption bltValuesOption; +BLT_EXTERN Blt_CustomOption bltValuePairsOption; +BLT_EXTERN Blt_CustomOption bltLinePenOption; +BLT_EXTERN Blt_CustomOption bltXAxisOption; +BLT_EXTERN Blt_CustomOption bltYAxisOption; + +#define DEF_LINE_ACTIVE_PEN "activeLine" +#define DEF_LINE_AXIS_X "x" +#define DEF_LINE_AXIS_Y "y" +#define DEF_LINE_DASHES (char *)NULL +#define DEF_LINE_DATA (char *)NULL +#define DEF_LINE_FILL_COLOR "defcolor" +#define DEF_LINE_HIDE "no" +#define DEF_LINE_LABEL (char *)NULL +#define DEF_LINE_LABEL_RELIEF "flat" +#define DEF_LINE_MAX_SYMBOLS "0" +#define DEF_LINE_OFFDASH_COLOR (char *)NULL +#define DEF_LINE_OUTLINE_COLOR "defcolor" +#define DEF_LINE_OUTLINE_WIDTH "1" +#define DEF_LINE_PATTERN_BG (char *)NULL +#define DEF_LINE_PATTERN_FG "black" +#define DEF_LINE_PEN_COLOR RGB_NAVYBLUE +#define DEF_LINE_PEN_DIRECTION "both" +#define DEF_LINE_PEN_WIDTH "1" +#define DEF_LINE_PIXELS "0.1i" +#define DEF_LINE_REDUCE "0.0" +#define DEF_LINE_SCALE_SYMBOLS "yes" +#define DEF_LINE_SMOOTH "linear" +#define DEF_LINE_STATE "normal" +#define DEF_LINE_STIPPLE (char *)NULL +#define DEF_LINE_STYLES "" +#define DEF_LINE_SYMBOL "circle" +#define DEF_LINE_TAGS "all" +#define DEF_LINE_X_DATA (char *)NULL +#define DEF_LINE_Y_DATA (char *)NULL + +#define DEF_LINE_ERRORBAR_COLOR "defcolor" +#define DEF_LINE_ERRORBAR_LINE_WIDTH "2" +#define DEF_LINE_ERRORBAR_CAP_WIDTH "2" +#define DEF_LINE_SHOW_ERRORBARS "both" + +#define DEF_PEN_ACTIVE_COLOR RGB_BLUE +#define DEF_PEN_DASHES (char *)NULL +#define DEF_PEN_FILL_COLOR "defcolor" +#define DEF_PEN_LINE_WIDTH "1" +#define DEF_PEN_NORMAL_COLOR RGB_NAVYBLUE +#define DEF_PEN_OFFDASH_COLOR (char *)NULL +#define DEF_PEN_OUTLINE_COLOR "defcolor" +#define DEF_PEN_OUTLINE_WIDTH "1" +#define DEF_PEN_PIXELS "0.1i" +#define DEF_PEN_SYMBOL "circle" +#define DEF_PEN_TYPE "line" +#define DEF_PEN_VALUE_ANCHOR "s" +#define DEF_PEN_VALUE_COLOR RGB_BLACK +#define DEF_PEN_VALUE_FONT STD_FONT_NUMBERS +#define DEF_PEN_VALUE_FORMAT "%g" +#define DEF_PEN_VALUE_ANGLE (char *)NULL +#define DEF_PEN_SHOW_VALUES "no" + +static Blt_ConfigSpec lineElemConfigSpecs[] = +{ + {BLT_CONFIG_CUSTOM, "-activepen", "activePen", "ActivePen", + DEF_LINE_ACTIVE_PEN, Blt_Offset(LineElement, activePenPtr), + BLT_CONFIG_NULL_OK, &bltLinePenOption}, + {BLT_CONFIG_COLOR, "-areaforeground", "areaForeground", "AreaForeground", + DEF_LINE_PATTERN_FG, Blt_Offset(LineElement, fillFgColor), + BLT_CONFIG_NULL_OK}, + {BLT_CONFIG_BACKGROUND, "-areabackground", "areaBackground", + "AreaBackground", DEF_LINE_PATTERN_BG, Blt_Offset(LineElement, fillBg), + BLT_CONFIG_NULL_OK}, + {BLT_CONFIG_LIST, "-bindtags", "bindTags", "BindTags", DEF_LINE_TAGS, + Blt_Offset(LineElement, obj.tags), BLT_CONFIG_NULL_OK}, + {BLT_CONFIG_COLOR, "-color", "color", "Color", DEF_LINE_PEN_COLOR, + Blt_Offset(LineElement, builtinPen.traceColor), 0}, + {BLT_CONFIG_DASHES, "-dashes", "dashes", "Dashes", DEF_LINE_DASHES, + Blt_Offset(LineElement, builtinPen.traceDashes), BLT_CONFIG_NULL_OK}, + {BLT_CONFIG_CUSTOM, "-data", "data", "Data", DEF_LINE_DATA, 0, 0, + &bltValuePairsOption}, + {BLT_CONFIG_CUSTOM, "-errorbarcolor", "errorBarColor", "ErrorBarColor", + DEF_LINE_ERRORBAR_COLOR, + Blt_Offset(LineElement, builtinPen.errorBarColor), 0, &bltColorOption}, + {BLT_CONFIG_PIXELS_NNEG,"-errorbarwidth", "errorBarWidth", "ErrorBarWidth", + DEF_LINE_ERRORBAR_LINE_WIDTH, + Blt_Offset(LineElement, builtinPen.errorBarLineWidth), + BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_PIXELS_NNEG, "-errorbarcap", "errorBarCap", "ErrorBarCap", + DEF_LINE_ERRORBAR_CAP_WIDTH, + Blt_Offset(LineElement, builtinPen.errorBarCapWidth), + BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_CUSTOM, "-fill", "fill", "Fill", DEF_LINE_FILL_COLOR, + Blt_Offset(LineElement, builtinPen.symbol.fillColor), + BLT_CONFIG_NULL_OK, &bltColorOption}, + {BLT_CONFIG_BITMASK, "-hide", "hide", "Hide", DEF_LINE_HIDE, + Blt_Offset(LineElement, flags), BLT_CONFIG_DONT_SET_DEFAULT, + (Blt_CustomOption *)HIDE}, + {BLT_CONFIG_STRING, "-label", "label", "Label", (char *)NULL, + Blt_Offset(LineElement, label), BLT_CONFIG_NULL_OK}, + {BLT_CONFIG_RELIEF, "-legendrelief", "legendRelief", "LegendRelief", + DEF_LINE_LABEL_RELIEF, Blt_Offset(LineElement, legendRelief), + BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_PIXELS_NNEG, "-linewidth", "lineWidth", "LineWidth", + DEF_LINE_PEN_WIDTH, Blt_Offset(LineElement, builtinPen.traceWidth), + BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_CUSTOM, "-mapx", "mapX", "MapX", + DEF_LINE_AXIS_X, Blt_Offset(LineElement, axes.x), 0, &bltXAxisOption}, + {BLT_CONFIG_CUSTOM, "-mapy", "mapY", "MapY", + DEF_LINE_AXIS_Y, Blt_Offset(LineElement, axes.y), 0, &bltYAxisOption}, + {BLT_CONFIG_INT_NNEG, "-maxsymbols", "maxSymbols", "MaxSymbols", + DEF_LINE_MAX_SYMBOLS, Blt_Offset(LineElement, reqMaxSymbols), + BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_CUSTOM, "-offdash", "offDash", "OffDash", + DEF_LINE_OFFDASH_COLOR, + Blt_Offset(LineElement, builtinPen.traceOffColor), + BLT_CONFIG_NULL_OK, &bltColorOption}, + {BLT_CONFIG_CUSTOM, "-outline", "outline", "Outline", + DEF_LINE_OUTLINE_COLOR, + Blt_Offset(LineElement, builtinPen.symbol.outlineColor), + 0, &bltColorOption}, + {BLT_CONFIG_PIXELS_NNEG, "-outlinewidth", "outlineWidth", "OutlineWidth", + DEF_LINE_OUTLINE_WIDTH, + Blt_Offset(LineElement, builtinPen.symbol.outlineWidth), + BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_CUSTOM, "-pen", "pen", "Pen", (char *)NULL, + Blt_Offset(LineElement, normalPenPtr), BLT_CONFIG_NULL_OK, + &bltLinePenOption}, + {BLT_CONFIG_PIXELS_NNEG, "-pixels", "pixels", "Pixels", DEF_LINE_PIXELS, + Blt_Offset(LineElement, builtinPen.symbol.size), GRAPH | STRIPCHART}, + {BLT_CONFIG_FLOAT, "-reduce", "reduce", "Reduce", + DEF_LINE_REDUCE, Blt_Offset(LineElement, rTolerance), + GRAPH | STRIPCHART | BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_BOOLEAN, "-scalesymbols", "scaleSymbols", "ScaleSymbols", + DEF_LINE_SCALE_SYMBOLS, Blt_Offset(LineElement, scaleSymbols), + BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_FILL, "-showerrorbars", "showErrorBars", "ShowErrorBars", + DEF_LINE_SHOW_ERRORBARS, + Blt_Offset(LineElement, builtinPen.errorBarShow), + BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_FILL, "-showvalues", "showValues", "ShowValues", + DEF_PEN_SHOW_VALUES, Blt_Offset(LineElement, builtinPen.valueShow), + BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_CUSTOM, "-smooth", "smooth", "Smooth", DEF_LINE_SMOOTH, + Blt_Offset(LineElement, reqSmooth), BLT_CONFIG_DONT_SET_DEFAULT, + &smoothOption}, + {BLT_CONFIG_STATE, "-state", "state", "State", DEF_LINE_STATE, + Blt_Offset(LineElement, state), BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_CUSTOM, "-styles", "styles", "Styles", DEF_LINE_STYLES, + Blt_Offset(LineElement, styles), 0, &bltLineStylesOption}, + {BLT_CONFIG_CUSTOM, "-symbol", "symbol", "Symbol", DEF_LINE_SYMBOL, + Blt_Offset(LineElement, builtinPen.symbol), + BLT_CONFIG_DONT_SET_DEFAULT, &symbolOption}, + {BLT_CONFIG_CUSTOM, "-trace", "trace", "Trace", DEF_LINE_PEN_DIRECTION, + Blt_Offset(LineElement, penDir), + BLT_CONFIG_DONT_SET_DEFAULT, &penDirOption}, + {BLT_CONFIG_ANCHOR, "-valueanchor", "valueAnchor", "ValueAnchor", + DEF_PEN_VALUE_ANCHOR, + Blt_Offset(LineElement, builtinPen.valueStyle.anchor), 0}, + {BLT_CONFIG_COLOR, "-valuecolor", "valueColor", "ValueColor", + DEF_PEN_VALUE_COLOR, + Blt_Offset(LineElement, builtinPen.valueStyle.color), 0}, + {BLT_CONFIG_FONT, "-valuefont", "valueFont", "ValueFont", + DEF_PEN_VALUE_FONT, + Blt_Offset(LineElement, builtinPen.valueStyle.font), 0}, + {BLT_CONFIG_STRING, "-valueformat", "valueFormat", "ValueFormat", + DEF_PEN_VALUE_FORMAT, Blt_Offset(LineElement, builtinPen.valueFormat), + BLT_CONFIG_NULL_OK}, + {BLT_CONFIG_FLOAT, "-valuerotate", "valueRotate", "ValueRotate", + DEF_PEN_VALUE_ANGLE, + Blt_Offset(LineElement, builtinPen.valueStyle.angle), 0}, + {BLT_CONFIG_CUSTOM, "-weights", "weights", "Weights", (char *)NULL, + Blt_Offset(LineElement, w), 0, &bltValuesOption}, + {BLT_CONFIG_CUSTOM, "-x", "xData", "XData", (char *)NULL, + Blt_Offset(LineElement, x), 0, &bltValuesOption}, + {BLT_CONFIG_CUSTOM, "-xdata", "xData", "XData", (char *)NULL, + Blt_Offset(LineElement, x), 0, &bltValuesOption}, + {BLT_CONFIG_CUSTOM, "-xerror", "xError", "XError", (char *)NULL, + Blt_Offset(LineElement, xError), 0, &bltValuesOption}, + {BLT_CONFIG_CUSTOM, "-xhigh", "xHigh", "XHigh", (char *)NULL, + Blt_Offset(LineElement, xHigh), 0, &bltValuesOption}, + {BLT_CONFIG_CUSTOM, "-xlow", "xLow", "XLow", (char *)NULL, + Blt_Offset(LineElement, xLow), 0, &bltValuesOption}, + {BLT_CONFIG_CUSTOM, "-y", "yData", "YData", (char *)NULL, + Blt_Offset(LineElement, y), 0, &bltValuesOption}, + {BLT_CONFIG_CUSTOM, "-ydata", "yData", "YData", (char *)NULL, + Blt_Offset(LineElement, y), 0, &bltValuesOption}, + {BLT_CONFIG_CUSTOM, "-yerror", "yError", "YError", (char *)NULL, + Blt_Offset(LineElement, yError), 0, &bltValuesOption}, + {BLT_CONFIG_CUSTOM, "-yhigh", "yHigh", "YHigh", (char *)NULL, + Blt_Offset(LineElement, yHigh), 0, &bltValuesOption}, + {BLT_CONFIG_CUSTOM, "-ylow", "yLow", "YLow", (char *)NULL, + Blt_Offset(LineElement, yLow), 0, &bltValuesOption}, + {BLT_CONFIG_END, NULL, NULL, NULL, NULL, 0, 0} +}; + + +static Blt_ConfigSpec stripElemConfigSpecs[] = +{ + {BLT_CONFIG_CUSTOM, "-activepen", "activePen", "ActivePen", + DEF_LINE_ACTIVE_PEN, Blt_Offset(LineElement, activePenPtr), + BLT_CONFIG_NULL_OK, &bltLinePenOption}, + {BLT_CONFIG_COLOR, "-areaforeground", "areaForeground", "areaForeground", + DEF_LINE_PATTERN_FG, Blt_Offset(LineElement, fillFgColor), + BLT_CONFIG_NULL_OK}, + {BLT_CONFIG_BACKGROUND, "-areabackground", "areaBackground", + "areaBackground", DEF_LINE_PATTERN_BG, Blt_Offset(LineElement, fillBg), + BLT_CONFIG_NULL_OK}, + {BLT_CONFIG_LIST, "-bindtags", "bindTags", "BindTags", DEF_LINE_TAGS, + Blt_Offset(LineElement, obj.tags), BLT_CONFIG_NULL_OK}, + {BLT_CONFIG_COLOR, "-color", "color", "Color", + DEF_LINE_PEN_COLOR, Blt_Offset(LineElement, builtinPen.traceColor), 0}, + {BLT_CONFIG_DASHES, "-dashes", "dashes", "Dashes", + DEF_LINE_DASHES, Blt_Offset(LineElement, builtinPen.traceDashes), + BLT_CONFIG_NULL_OK}, + {BLT_CONFIG_CUSTOM, "-data", "data", "Data", DEF_LINE_DATA, 0, 0, + &bltValuePairsOption}, + {BLT_CONFIG_CUSTOM, "-errorbarcolor", "errorBarColor", "ErrorBarColor", + DEF_LINE_ERRORBAR_COLOR, + Blt_Offset(LineElement, builtinPen.errorBarColor), 0, &bltColorOption}, + {BLT_CONFIG_PIXELS_NNEG, "-errorbarwidth", "errorBarWidth", "ErrorBarWidth", + DEF_LINE_ERRORBAR_LINE_WIDTH, + Blt_Offset(LineElement, builtinPen.errorBarLineWidth), + BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_PIXELS_NNEG, "-errorbarcap", "errorBarCap", "ErrorBarCap", + DEF_LINE_ERRORBAR_CAP_WIDTH, + Blt_Offset(LineElement, builtinPen.errorBarCapWidth), + BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_CUSTOM, "-fill", "fill", "Fill", DEF_LINE_FILL_COLOR, + Blt_Offset(LineElement, builtinPen.symbol.fillColor), + BLT_CONFIG_NULL_OK, &bltColorOption}, + {BLT_CONFIG_BITMASK, "-hide", "hide", "Hide", DEF_LINE_HIDE, + Blt_Offset(LineElement, flags), BLT_CONFIG_DONT_SET_DEFAULT, + (Blt_CustomOption *)HIDE}, + {BLT_CONFIG_STRING, "-label", "label", "Label", (char *)NULL, + Blt_Offset(LineElement, label), BLT_CONFIG_NULL_OK}, + {BLT_CONFIG_RELIEF, "-legendrelief", "legendRelief", "LegendRelief", + DEF_LINE_LABEL_RELIEF, Blt_Offset(LineElement, legendRelief), + BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_PIXELS_NNEG, "-linewidth", "lineWidth", "LineWidth", + DEF_LINE_PEN_WIDTH, Blt_Offset(LineElement, builtinPen.traceWidth), + BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_CUSTOM, "-mapx", "mapX", "MapX", DEF_LINE_AXIS_X, + Blt_Offset(LineElement, axes.x), 0, &bltXAxisOption}, + {BLT_CONFIG_CUSTOM, "-mapy", "mapY", "MapY", DEF_LINE_AXIS_Y, + Blt_Offset(LineElement, axes.y), 0, &bltYAxisOption}, + {BLT_CONFIG_INT_NNEG, "-maxsymbols", "maxSymbols", "MaxSymbols", + DEF_LINE_MAX_SYMBOLS, Blt_Offset(LineElement, reqMaxSymbols), + BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_CUSTOM, "-offdash", "offDash", "OffDash", + DEF_LINE_OFFDASH_COLOR, Blt_Offset(LineElement, builtinPen.traceOffColor), + BLT_CONFIG_NULL_OK, &bltColorOption}, + {BLT_CONFIG_CUSTOM, "-outline", "outline", "Outline", + DEF_LINE_OUTLINE_COLOR, + Blt_Offset(LineElement, builtinPen.symbol.outlineColor), 0, + &bltColorOption}, + {BLT_CONFIG_PIXELS_NNEG, "-outlinewidth", "outlineWidth", "OutlineWidth", + DEF_LINE_OUTLINE_WIDTH, + Blt_Offset(LineElement, builtinPen.symbol.outlineWidth), + BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_CUSTOM, "-pen", "pen", "Pen", (char *)NULL, + Blt_Offset(LineElement, normalPenPtr), BLT_CONFIG_NULL_OK, + &bltLinePenOption}, + {BLT_CONFIG_PIXELS_NNEG, "-pixels", "pixels", "Pixels", DEF_LINE_PIXELS, + Blt_Offset(LineElement, builtinPen.symbol.size), 0}, + {BLT_CONFIG_BOOLEAN, "-scalesymbols", "scaleSymbols", "ScaleSymbols", + DEF_LINE_SCALE_SYMBOLS, Blt_Offset(LineElement, scaleSymbols), + BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_FILL, "-showerrorbars", "showErrorBars", "ShowErrorBars", + DEF_LINE_SHOW_ERRORBARS, + Blt_Offset(LineElement, builtinPen.errorBarShow), + BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_FILL, "-showvalues", "showValues", "ShowValues", + DEF_PEN_SHOW_VALUES, Blt_Offset(LineElement, builtinPen.valueShow), + BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_CUSTOM, "-smooth", "smooth", "Smooth", DEF_LINE_SMOOTH, + Blt_Offset(LineElement, reqSmooth), BLT_CONFIG_DONT_SET_DEFAULT, + &smoothOption}, + {BLT_CONFIG_CUSTOM, "-styles", "styles", "Styles", DEF_LINE_STYLES, + Blt_Offset(LineElement, styles), 0, &bltLineStylesOption}, + {BLT_CONFIG_CUSTOM, "-symbol", "symbol", "Symbol", DEF_LINE_SYMBOL, + Blt_Offset(LineElement, builtinPen.symbol), + BLT_CONFIG_DONT_SET_DEFAULT, &symbolOption}, + {BLT_CONFIG_ANCHOR, "-valueanchor", "valueAnchor", "ValueAnchor", + DEF_PEN_VALUE_ANCHOR, + Blt_Offset(LineElement, builtinPen.valueStyle.anchor), 0}, + {BLT_CONFIG_COLOR, "-valuecolor", "valueColor", "ValueColor", + DEF_PEN_VALUE_COLOR, + Blt_Offset(LineElement, builtinPen.valueStyle.color), 0}, + {BLT_CONFIG_FONT, "-valuefont", "valueFont", "ValueFont", + DEF_PEN_VALUE_FONT, + Blt_Offset(LineElement, builtinPen.valueStyle.font), 0}, + {BLT_CONFIG_STRING, "-valueformat", "valueFormat", "ValueFormat", + DEF_PEN_VALUE_FORMAT, Blt_Offset(LineElement, builtinPen.valueFormat), + BLT_CONFIG_NULL_OK}, + {BLT_CONFIG_FLOAT, "-valuerotate", "valueRotate", "ValueRotate", + DEF_PEN_VALUE_ANGLE, + Blt_Offset(LineElement, builtinPen.valueStyle.angle),0}, + {BLT_CONFIG_CUSTOM, "-weights", "weights", "Weights", (char *)NULL, + Blt_Offset(LineElement, w), 0, &bltValuesOption}, + {BLT_CONFIG_CUSTOM, "-x", "xData", "XData", (char *)NULL, + Blt_Offset(LineElement, x), 0, &bltValuesOption}, + {BLT_CONFIG_CUSTOM, "-xdata", "xData", "XData", (char *)NULL, + Blt_Offset(LineElement, x), 0, &bltValuesOption}, + {BLT_CONFIG_CUSTOM, "-y", "yData", "YData", (char *)NULL, + Blt_Offset(LineElement, y), 0, &bltValuesOption}, + {BLT_CONFIG_CUSTOM, "-xerror", "xError", "XError", (char *)NULL, + Blt_Offset(LineElement, xError), 0, &bltValuesOption}, + {BLT_CONFIG_CUSTOM, "-ydata", "yData", "YData", (char *)NULL, + Blt_Offset(LineElement, y), 0, &bltValuesOption}, + {BLT_CONFIG_CUSTOM, "-yerror", "yError", "YError", (char *)NULL, + Blt_Offset(LineElement, yError), 0, &bltValuesOption}, + {BLT_CONFIG_CUSTOM, "-xhigh", "xHigh", "XHigh", (char *)NULL, + Blt_Offset(LineElement, xHigh), 0, &bltValuesOption}, + {BLT_CONFIG_CUSTOM, "-xlow", "xLow", "XLow", (char *)NULL, + Blt_Offset(LineElement, xLow), 0, &bltValuesOption}, + {BLT_CONFIG_CUSTOM, "-yhigh", "yHigh", "YHigh", (char *)NULL, + Blt_Offset(LineElement, xHigh), 0, &bltValuesOption}, + {BLT_CONFIG_CUSTOM, "-ylow", "yLow", "YLow", (char *)NULL, + Blt_Offset(LineElement, yLow), 0, &bltValuesOption}, + {BLT_CONFIG_END, NULL, NULL, NULL, NULL, 0, 0} +}; + +static Blt_ConfigSpec linePenConfigSpecs[] = +{ + {BLT_CONFIG_COLOR, "-color", "color", "Color", DEF_PEN_ACTIVE_COLOR, + Blt_Offset(LinePen, traceColor), ACTIVE_PEN}, + {BLT_CONFIG_COLOR, "-color", "color", "Color", DEF_PEN_NORMAL_COLOR, + Blt_Offset(LinePen, traceColor), NORMAL_PEN}, + {BLT_CONFIG_DASHES, "-dashes", "dashes", "Dashes", DEF_PEN_DASHES, + Blt_Offset(LinePen, traceDashes), BLT_CONFIG_NULL_OK | ALL_PENS}, + {BLT_CONFIG_CUSTOM, "-errorbarcolor", "errorBarColor", "ErrorBarColor", + DEF_LINE_ERRORBAR_COLOR, Blt_Offset(LinePen, errorBarColor), + ALL_PENS, &bltColorOption}, + {BLT_CONFIG_PIXELS_NNEG, "-errorbarwidth", "errorBarWidth", "ErrorBarWidth", + DEF_LINE_ERRORBAR_LINE_WIDTH, Blt_Offset(LinePen, errorBarLineWidth), + ALL_PENS | BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_PIXELS_NNEG, "-errorbarcap", "errorBarCap", "ErrorBarCap", + DEF_LINE_ERRORBAR_CAP_WIDTH, Blt_Offset(LinePen, errorBarCapWidth), + BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_CUSTOM, "-fill", "fill", "Fill", DEF_PEN_FILL_COLOR, + Blt_Offset(LinePen, symbol.fillColor), BLT_CONFIG_NULL_OK | ALL_PENS, + &bltColorOption}, + {BLT_CONFIG_PIXELS_NNEG, "-linewidth", "lineWidth", "LineWidth", + (char *)NULL, Blt_Offset(LinePen, traceWidth), + ALL_PENS| BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_CUSTOM, "-offdash", "offDash", "OffDash", DEF_PEN_OFFDASH_COLOR, + Blt_Offset(LinePen, traceOffColor), BLT_CONFIG_NULL_OK | ALL_PENS, + &bltColorOption}, + {BLT_CONFIG_CUSTOM, "-outline", "outline", "Outline", DEF_PEN_OUTLINE_COLOR, + Blt_Offset(LinePen, symbol.outlineColor), ALL_PENS, &bltColorOption}, + {BLT_CONFIG_PIXELS_NNEG, "-outlinewidth", "outlineWidth", "OutlineWidth", + DEF_PEN_OUTLINE_WIDTH, Blt_Offset(LinePen, symbol.outlineWidth), + BLT_CONFIG_DONT_SET_DEFAULT | ALL_PENS}, + {BLT_CONFIG_PIXELS_NNEG, "-pixels", "pixels", "Pixels", DEF_PEN_PIXELS, + Blt_Offset(LinePen, symbol.size), ALL_PENS}, + {BLT_CONFIG_FILL, "-showerrorbars", "showErrorBars", "ShowErrorBars", + DEF_LINE_SHOW_ERRORBARS, Blt_Offset(LinePen, errorBarShow), + BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_FILL, "-showvalues", "showValues", "ShowValues", + DEF_PEN_SHOW_VALUES, Blt_Offset(LinePen, valueShow), + ALL_PENS | BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_CUSTOM, "-symbol", "symbol", "Symbol", DEF_PEN_SYMBOL, + Blt_Offset(LinePen, symbol), BLT_CONFIG_DONT_SET_DEFAULT | ALL_PENS, + &symbolOption}, + {BLT_CONFIG_STRING, "-type", (char *)NULL, (char *)NULL, DEF_PEN_TYPE, + Blt_Offset(Pen, typeId), ALL_PENS | BLT_CONFIG_NULL_OK}, + {BLT_CONFIG_ANCHOR, "-valueanchor", "valueAnchor", "ValueAnchor", + DEF_PEN_VALUE_ANCHOR, Blt_Offset(LinePen, valueStyle.anchor), ALL_PENS}, + {BLT_CONFIG_COLOR, "-valuecolor", "valueColor", "ValueColor", + DEF_PEN_VALUE_COLOR, Blt_Offset(LinePen, valueStyle.color), ALL_PENS}, + {BLT_CONFIG_FONT, "-valuefont", "valueFont", "ValueFont", + DEF_PEN_VALUE_FONT, Blt_Offset(LinePen, valueStyle.font), ALL_PENS}, + {BLT_CONFIG_STRING, "-valueformat", "valueFormat", "ValueFormat", + DEF_PEN_VALUE_FORMAT, Blt_Offset(LinePen, valueFormat), + ALL_PENS | BLT_CONFIG_NULL_OK}, + {BLT_CONFIG_FLOAT, "-valuerotate", "valueRotate", "ValueRotate", + DEF_PEN_VALUE_ANGLE, Blt_Offset(LinePen, valueStyle.angle), ALL_PENS}, + {BLT_CONFIG_END, NULL, NULL, NULL, NULL, 0, 0} +}; + +typedef double (DistanceProc)(int x, int y, Point2d *p, Point2d *q, Point2d *t); + +/* Forward declarations */ +static PenConfigureProc ConfigurePenProc; +static PenDestroyProc DestroyPenProc; +static ElementClosestProc ClosestLineProc; +static ElementConfigProc ConfigureLineProc; +static ElementDestroyProc DestroyLineProc; +static ElementDrawProc DrawActiveLineProc; +static ElementDrawProc DrawNormalLineProc; +static ElementDrawSymbolProc DrawSymbolProc; +static ElementExtentsProc GetLineExtentsProc; +static ElementToPostScriptProc ActiveLineToPostScriptProc; +static ElementToPostScriptProc NormalLineToPostScriptProc; +static ElementSymbolToPostScriptProc SymbolToPostScriptProc; +static ElementMapProc MapLineProc; +static DistanceProc DistanceToYProc; +static DistanceProc DistanceToXProc; +static DistanceProc DistanceToLineProc; +static Blt_BackgroundChangedProc BackgroundChangedProc; + +#ifdef WIN32 + +static int tkpWinRopModes[] = +{ + R2_BLACK, /* GXclear */ + R2_MASKPEN, /* GXand */ + R2_MASKPENNOT, /* GXandReverse */ + R2_COPYPEN, /* GXcopy */ + R2_MASKNOTPEN, /* GXandInverted */ + R2_NOT, /* GXnoop */ + R2_XORPEN, /* GXxor */ + R2_MERGEPEN, /* GXor */ + R2_NOTMERGEPEN, /* GXnor */ + R2_NOTXORPEN, /* GXequiv */ + R2_NOT, /* GXinvert */ + R2_MERGEPENNOT, /* GXorReverse */ + R2_NOTCOPYPEN, /* GXcopyInverted */ + R2_MERGENOTPEN, /* GXorInverted */ + R2_NOTMASKPEN, /* GXnand */ + R2_WHITE /* GXset */ +}; + +#endif + +#ifndef notdef +INLINE static int +Round(double x) +{ + return (int) (x + ((x < 0.0) ? -0.5 : 0.5)); +} +#else +#define Round Round +#endif +/* + *--------------------------------------------------------------------------- + * Custom configuration option (parse and print) routines + *--------------------------------------------------------------------------- + */ + +static void +DestroySymbol(Display *display, Symbol *symbolPtr) +{ + if (symbolPtr->image != NULL) { + Tk_FreeImage(symbolPtr->image); + symbolPtr->image = NULL; + } + if (symbolPtr->bitmap != None) { + Tk_FreeBitmap(display, symbolPtr->bitmap); + symbolPtr->bitmap = None; + } + if (symbolPtr->mask != None) { + Tk_FreeBitmap(display, symbolPtr->mask); + symbolPtr->mask = None; + } + symbolPtr->type = SYMBOL_NONE; +} + +/* + *--------------------------------------------------------------------------- + * + * ImageChangedProc + * + * + * Results: + * None. + * + *--------------------------------------------------------------------------- + */ +/* ARGSUSED */ +static void +ImageChangedProc( + ClientData clientData, + int x, int y, int w, int h, /* Not used. */ + int imageWidth, int imageHeight) /* Not used. */ +{ + Element *elemPtr; + Graph *graphPtr; + + elemPtr = clientData; + elemPtr->flags |= MAP_ITEM; + graphPtr = elemPtr->obj.graphPtr; + graphPtr->flags |= CACHE_DIRTY; + Blt_EventuallyRedrawGraph(graphPtr); +} + +/*ARGSUSED*/ +static void +FreeSymbolProc( + ClientData clientData, /* Not used. */ + Display *display, /* Not used. */ + char *widgRec, + int offset) +{ + Symbol *symbolPtr = (Symbol *)(widgRec + offset); + + DestroySymbol(display, symbolPtr); +} + +/* + *--------------------------------------------------------------------------- + * + * ObjToSymbol -- + * + * Convert the string representation of a line style or symbol name into + * its numeric form. + * + * Results: + * The return value is a standard TCL result. The symbol type is written + * into the widget record. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +ObjToSymbolProc( + ClientData clientData, /* Not used. */ + Tcl_Interp *interp, /* Interpreter to send results back + * to */ + Tk_Window tkwin, /* Not used. */ + Tcl_Obj *objPtr, /* String representing symbol type */ + char *widgRec, /* Element information record */ + int offset, /* Offset to field in structure */ + int flags) /* Not used. */ +{ + Symbol *symbolPtr = (Symbol *)(widgRec + offset); + const char *string; + + { + int length; + GraphSymbolType *p; + char c; + + string = Tcl_GetStringFromObj(objPtr, &length); + if (length == 0) { + DestroySymbol(Tk_Display(tkwin), symbolPtr); + symbolPtr->type = SYMBOL_NONE; + return TCL_OK; + } + c = string[0]; + for (p = graphSymbols; p->name != NULL; p++) { + if (length < p->minChars) { + continue; + } + if ((c == p->name[0]) && (strncmp(string, p->name, length) == 0)) { + DestroySymbol(Tk_Display(tkwin), symbolPtr); + symbolPtr->type = p->type; + return TCL_OK; + } + } + } + { + Tk_Image tkImage; + Element *elemPtr = (Element *)widgRec; + + tkImage = Tk_GetImage(interp, tkwin, string, ImageChangedProc, elemPtr); + if (tkImage != NULL) { + DestroySymbol(Tk_Display(tkwin), symbolPtr); + symbolPtr->image = tkImage; + symbolPtr->type = SYMBOL_IMAGE; + return TCL_OK; + } + } + { + Pixmap bitmap, mask; + Tcl_Obj **objv; + int objc; + + if ((Tcl_ListObjGetElements(NULL, objPtr, &objc, &objv) != TCL_OK) || + (objc > 2)) { + goto error; + } + bitmap = mask = None; + if (objc > 0) { + bitmap = Tk_AllocBitmapFromObj((Tcl_Interp *)NULL, tkwin, objv[0]); + if (bitmap == None) { + goto error; + } + } + if (objc > 1) { + mask = Tk_AllocBitmapFromObj((Tcl_Interp *)NULL, tkwin, objv[1]); + if (mask == None) { + goto error; + } + } + DestroySymbol(Tk_Display(tkwin), symbolPtr); + symbolPtr->bitmap = bitmap; + symbolPtr->mask = mask; + symbolPtr->type = SYMBOL_BITMAP; + return TCL_OK; + } + error: + Tcl_AppendResult(interp, "bad symbol \"", string, + "\": should be \"none\", \"circle\", \"square\", \"diamond\", " + "\"plus\", \"cross\", \"splus\", \"scross\", \"triangle\", " + "\"arrow\" or the name of a bitmap", (char *)NULL); + return TCL_ERROR; +} + +/* + *--------------------------------------------------------------------------- + * + * SymbolToObj -- + * + * Convert the symbol value into a string. + * + * Results: + * The string representing the symbol type or line style is returned. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static Tcl_Obj * +SymbolToObjProc( + ClientData clientData, /* Not used. */ + Tcl_Interp *interp, + Tk_Window tkwin, + char *widgRec, /* Element information record */ + int offset, /* Offset to field in structure */ + int flags) /* Not used. */ +{ + Symbol *symbolPtr = (Symbol *)(widgRec + offset); + + if (symbolPtr->type == SYMBOL_BITMAP) { + Tcl_Obj *listObjPtr, *objPtr; + const char *name; + + listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL); + name = Tk_NameOfBitmap(Tk_Display(tkwin), symbolPtr->bitmap); + objPtr = Tcl_NewStringObj(name, -1); + Tcl_ListObjAppendElement(interp, listObjPtr, objPtr); + if (symbolPtr->mask == None) { + objPtr = Tcl_NewStringObj("", -1); + } else { + name = Tk_NameOfBitmap(Tk_Display(tkwin), symbolPtr->mask); + objPtr = Tcl_NewStringObj(name, -1); + } + Tcl_ListObjAppendElement(interp, listObjPtr, objPtr); + return listObjPtr; + } else { + GraphSymbolType *p; + + for (p = graphSymbols; p->name != NULL; p++) { + if (p->type == symbolPtr->type) { + return Tcl_NewStringObj(p->name, -1); + } + } + return Tcl_NewStringObj("?unknown symbol type?", -1); + } +} + +/* + *--------------------------------------------------------------------------- + * + * NameOfSmooth -- + * + * Converts the smooth value into its string representation. + * + * Results: + * The static string representing the smooth type is returned. + * + *--------------------------------------------------------------------------- + */ +static const char * +NameOfSmooth(Smoothing value) +{ + SmoothingInfo *siPtr; + + for (siPtr = smoothingInfo; siPtr->name != NULL; siPtr++) { + if (siPtr->value == value) { + return siPtr->name; + } + } + return "unknown smooth value"; +} + +/* + *--------------------------------------------------------------------------- + * + * ObjToSmooth -- + * + * Convert the string representation of a line style or smooth name + * into its numeric form. + * + * Results: + * The return value is a standard TCL result. The smooth type is + * written into the widget record. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +ObjToSmoothProc( + ClientData clientData, /* Not used. */ + Tcl_Interp *interp, /* Interpreter to send results back + * to */ + Tk_Window tkwin, /* Not used. */ + Tcl_Obj *objPtr, /* String representing smooth type */ + char *widgRec, /* Element information record */ + int offset, /* Offset to field in structure */ + int flags) /* Not used. */ +{ + Smoothing *valuePtr = (Smoothing *)(widgRec + offset); + SmoothingInfo *siPtr; + const char *string; + char c; + + string = Tcl_GetString(objPtr); + c = string[0]; + for (siPtr = smoothingInfo; siPtr->name != NULL; siPtr++) { + if ((c == siPtr->name[0]) && (strcmp(string, siPtr->name) == 0)) { + *valuePtr = siPtr->value; + return TCL_OK; + } + } + Tcl_AppendResult(interp, "bad smooth value \"", string, "\": should be \ +linear, step, natural, or quadratic", (char *)NULL); + return TCL_ERROR; +} + +/* + *--------------------------------------------------------------------------- + * + * SmoothToObj -- + * + * Convert the smooth value into a string. + * + * Results: + * The string representing the smooth type or line style is returned. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static Tcl_Obj * +SmoothToObjProc( + ClientData clientData, /* Not used. */ + Tcl_Interp *interp, /* Not used. */ + Tk_Window tkwin, /* Not used. */ + char *widgRec, /* Element information record */ + int offset, /* Offset to field in structure */ + int flags) /* Not used. */ +{ + int smooth = *(int *)(widgRec + offset); + + return Tcl_NewStringObj(NameOfSmooth(smooth), -1); +} + +/* + *--------------------------------------------------------------------------- + * + * ObjToPenDir -- + * + * Convert the string representation of a line style or symbol name + * into its numeric form. + * + * Results: + * The return value is a standard TCL result. The symbol type is + * written into the widget record. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +ObjToPenDirProc( + ClientData clientData, /* Not used. */ + Tcl_Interp *interp, /* Interpreter to send results back + * to */ + Tk_Window tkwin, /* Not used. */ + Tcl_Obj *objPtr, /* String representing pen direction */ + char *widgRec, /* Element information record */ + int offset, /* Offset to field in structure */ + int flags) /* Not used. */ +{ + int *penDirPtr = (int *)(widgRec + offset); + int length; + char c; + char *string; + + string = Tcl_GetStringFromObj(objPtr, &length); + c = string[0]; + if ((c == 'i') && (strncmp(string, "increasing", length) == 0)) { + *penDirPtr = PEN_INCREASING; + } else if ((c == 'd') && (strncmp(string, "decreasing", length) == 0)) { + *penDirPtr = PEN_DECREASING; + } else if ((c == 'b') && (strncmp(string, "both", length) == 0)) { + *penDirPtr = PEN_BOTH_DIRECTIONS; + } else { + Tcl_AppendResult(interp, "bad trace value \"", string, + "\" : should be \"increasing\", \"decreasing\", or \"both\"", + (char *)NULL); + return TCL_ERROR; + } + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * NameOfPenDir -- + * + * Convert the pen direction into a string. + * + * Results: + * The static string representing the pen direction is returned. + * + *--------------------------------------------------------------------------- + */ +static const char * +NameOfPenDir(int penDir) +{ + switch (penDir) { + case PEN_INCREASING: + return "increasing"; + case PEN_DECREASING: + return "decreasing"; + case PEN_BOTH_DIRECTIONS: + return "both"; + default: + return "unknown trace direction"; + } +} + +/* + *--------------------------------------------------------------------------- + * + * PenDirToObj -- + * + * Convert the pen direction into a string. + * + * Results: + * The string representing the pen drawing direction is returned. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static Tcl_Obj * +PenDirToObjProc( + ClientData clientData, /* Not used. */ + Tcl_Interp *interp, /* Not used. */ + Tk_Window tkwin, /* Not used. */ + char *widgRec, /* Element information record */ + int offset, /* Offset to field in structure */ + int flags) /* Not used. */ +{ + int penDir = *(int *)(widgRec + offset); + + return Tcl_NewStringObj(NameOfPenDir(penDir), -1); +} + + +/* + * Reset the number of points and segments, in case there are no segments or + * points + */ +static void +ResetStylePalette(Blt_Chain styles) +{ + Blt_ChainLink link; + + for (link = Blt_Chain_FirstLink(styles); link != NULL; + link = Blt_Chain_NextLink(link)) { + LineStyle *stylePtr; + + stylePtr = Blt_Chain_GetValue(link); + stylePtr->lines.length = stylePtr->symbolPts.length = 0; + stylePtr->xeb.length = stylePtr->yeb.length = 0; + } +} + +/* + *--------------------------------------------------------------------------- + * + * ConfigurePenProc -- + * + * Sets up the appropriate configuration parameters in the GC. It is + * assumed the parameters have been previously set by a call to + * Blt_ConfigureWidget. + * + * Results: + * The return value is a standard TCL result. If TCL_ERROR is returned, + * then interp->result contains an error message. + * + * Side effects: + * Configuration information such as line width, line style, color + * etc. get set in a new GC. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +ConfigurePenProc(Graph *graphPtr, Pen *penPtr) +{ + LinePen *lpPtr = (LinePen *)penPtr; + unsigned long gcMask; + GC newGC; + XGCValues gcValues; + XColor *colorPtr; + + /* + * Set the outline GC for this pen: GCForeground is outline color. + * GCBackground is the fill color (only used for bitmap symbols). + */ + gcMask = (GCLineWidth | GCForeground); + colorPtr = lpPtr->symbol.outlineColor; + if (colorPtr == COLOR_DEFAULT) { + colorPtr = lpPtr->traceColor; + } + gcValues.foreground = colorPtr->pixel; + if (lpPtr->symbol.type == SYMBOL_BITMAP) { + colorPtr = lpPtr->symbol.fillColor; + if (colorPtr == COLOR_DEFAULT) { + colorPtr = lpPtr->traceColor; + } + /* + * Set a clip mask if either + * 1) no background color was designated or + * 2) a masking bitmap was specified. + * + * These aren't necessarily the bitmaps we'll be using for clipping. But + * this makes it unlikely that anyone else will be sharing this GC when + * we set the clip origin (at the time the bitmap is drawn). + */ + if (colorPtr != NULL) { + gcValues.background = colorPtr->pixel; + gcMask |= GCBackground; + if (lpPtr->symbol.mask != None) { + gcValues.clip_mask = lpPtr->symbol.mask; + gcMask |= GCClipMask; + } + } else { + gcValues.clip_mask = lpPtr->symbol.bitmap; + gcMask |= GCClipMask; + } + } + gcValues.line_width = LineWidth(lpPtr->symbol.outlineWidth); + newGC = Tk_GetGC(graphPtr->tkwin, gcMask, &gcValues); + if (lpPtr->symbol.outlineGC != NULL) { + Tk_FreeGC(graphPtr->display, lpPtr->symbol.outlineGC); + } + lpPtr->symbol.outlineGC = newGC; + + /* Fill GC for symbols: GCForeground is fill color */ + + gcMask = (GCLineWidth | GCForeground); + colorPtr = lpPtr->symbol.fillColor; + if (colorPtr == COLOR_DEFAULT) { + colorPtr = lpPtr->traceColor; + } + newGC = NULL; + if (colorPtr != NULL) { + gcValues.foreground = colorPtr->pixel; + newGC = Tk_GetGC(graphPtr->tkwin, gcMask, &gcValues); + } + if (lpPtr->symbol.fillGC != NULL) { + Tk_FreeGC(graphPtr->display, lpPtr->symbol.fillGC); + } + lpPtr->symbol.fillGC = newGC; + + /* Line segments */ + + gcMask = (GCLineWidth | GCForeground | GCLineStyle | GCCapStyle | + GCJoinStyle); + gcValues.cap_style = CapButt; + gcValues.join_style = JoinRound; + gcValues.line_style = LineSolid; + gcValues.line_width = LineWidth(lpPtr->traceWidth); + + colorPtr = lpPtr->traceOffColor; + if (colorPtr == COLOR_DEFAULT) { + colorPtr = lpPtr->traceColor; + } + if (colorPtr != NULL) { + gcMask |= GCBackground; + gcValues.background = colorPtr->pixel; + } + gcValues.foreground = lpPtr->traceColor->pixel; + if (LineIsDashed(lpPtr->traceDashes)) { + gcValues.line_width = lpPtr->traceWidth; + gcValues.line_style = + (colorPtr == NULL) ? LineOnOffDash : LineDoubleDash; + } + newGC = Blt_GetPrivateGC(graphPtr->tkwin, gcMask, &gcValues); + if (lpPtr->traceGC != NULL) { + Blt_FreePrivateGC(graphPtr->display, lpPtr->traceGC); + } + if (LineIsDashed(lpPtr->traceDashes)) { + lpPtr->traceDashes.offset = lpPtr->traceDashes.values[0] / 2; + Blt_SetDashes(graphPtr->display, newGC, &lpPtr->traceDashes); + } + lpPtr->traceGC = newGC; + + gcMask = (GCLineWidth | GCForeground); + colorPtr = lpPtr->errorBarColor; + if (colorPtr == COLOR_DEFAULT) { + colorPtr = lpPtr->traceColor; + } + gcValues.line_width = LineWidth(lpPtr->errorBarLineWidth); + gcValues.foreground = colorPtr->pixel; + newGC = Tk_GetGC(graphPtr->tkwin, gcMask, &gcValues); + if (lpPtr->errorBarGC != NULL) { + Tk_FreeGC(graphPtr->display, lpPtr->errorBarGC); + } + lpPtr->errorBarGC = newGC; + + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * DestroyPenProc -- + * + * Release memory and resources allocated for the style. + * + * Results: + * None. + * + * Side effects: + * Everything associated with the pen style is freed up. + * + *--------------------------------------------------------------------------- + */ +static void +DestroyPenProc(Graph *graphPtr, Pen *basePtr) +{ + LinePen *penPtr = (LinePen *)basePtr; + + Blt_Ts_FreeStyle(graphPtr->display, &penPtr->valueStyle); + if (penPtr->symbol.outlineGC != NULL) { + Tk_FreeGC(graphPtr->display, penPtr->symbol.outlineGC); + } + if (penPtr->symbol.fillGC != NULL) { + Tk_FreeGC(graphPtr->display, penPtr->symbol.fillGC); + } + if (penPtr->errorBarGC != NULL) { + Tk_FreeGC(graphPtr->display, penPtr->errorBarGC); + } + if (penPtr->traceGC != NULL) { + Blt_FreePrivateGC(graphPtr->display, penPtr->traceGC); + } + if (penPtr->symbol.bitmap != None) { + Tk_FreeBitmap(graphPtr->display, penPtr->symbol.bitmap); + penPtr->symbol.bitmap = None; + } + if (penPtr->symbol.mask != None) { + Tk_FreeBitmap(graphPtr->display, penPtr->symbol.mask); + penPtr->symbol.mask = None; + } +} + + +static void +InitLinePen(LinePen *penPtr) +{ + Blt_Ts_InitStyle(penPtr->valueStyle); + penPtr->errorBarLineWidth = 2; + penPtr->errorBarShow = SHOW_BOTH; + penPtr->configProc = ConfigurePenProc; + penPtr->configSpecs = linePenConfigSpecs; + penPtr->destroyProc = DestroyPenProc; + penPtr->flags = NORMAL_PEN; + penPtr->name = ""; + penPtr->symbol.bitmap = penPtr->symbol.mask = None; + penPtr->symbol.outlineColor = penPtr->symbol.fillColor = COLOR_DEFAULT; + penPtr->symbol.outlineWidth = penPtr->traceWidth = 1; + penPtr->symbol.type = SYMBOL_CIRCLE; + penPtr->valueShow = SHOW_NONE; +} + +Pen * +Blt_LinePen(const char *penName) +{ + LinePen *penPtr; + + penPtr = Blt_AssertCalloc(1, sizeof(LinePen)); + InitLinePen(penPtr); + penPtr->name = Blt_AssertStrdup(penName); + penPtr->classId = CID_ELEM_LINE; + if (strcmp(penName, "activeLine") == 0) { + penPtr->flags = ACTIVE_PEN; + } + return (Pen *)penPtr; +} + +/* + *--------------------------------------------------------------------------- + * + * In this section, the routines deal with building and filling the + * element's data structures with transformed screen coordinates. They are + * triggered from TranformLine which is called whenever the data or + * coordinates axes have changed and new screen coordinates need to be + * calculated. + * + *--------------------------------------------------------------------------- + */ + +/* + *--------------------------------------------------------------------------- + * + * ScaleSymbol -- + * + * Returns the scaled size for the line element. Scaling depends upon when + * the base line ranges for the element were set and the current range of + * the graph. + * + * Results: + * The new size of the symbol, after considering how much the graph has + * been scaled, is returned. + * + *--------------------------------------------------------------------------- + */ +static int +ScaleSymbol(LineElement *elemPtr, int normalSize) +{ + int maxSize; + double scale; + int newSize; + + scale = 1.0; + if (elemPtr->scaleSymbols) { + double xRange, yRange; + + xRange = (elemPtr->axes.x->max - elemPtr->axes.x->min); + yRange = (elemPtr->axes.y->max - elemPtr->axes.y->min); + if (elemPtr->flags & SCALE_SYMBOL) { + /* Save the ranges as a baseline for future scaling. */ + elemPtr->xRange = xRange; + elemPtr->yRange = yRange; + elemPtr->flags &= ~SCALE_SYMBOL; + } else { + double xScale, yScale; + + /* Scale the symbol by the smallest change in the X or Y axes */ + xScale = elemPtr->xRange / xRange; + yScale = elemPtr->yRange / yRange; + scale = MIN(xScale, yScale); + } + } + newSize = Round(normalSize * scale); + + /* + * Don't let the size of symbols go unbounded. Both X and Win32 drawing + * routines assume coordinates to be a signed short int. + */ + maxSize = (int)MIN(elemPtr->obj.graphPtr->hRange, + elemPtr->obj.graphPtr->vRange); + if (newSize > maxSize) { + newSize = maxSize; + } + + /* Make the symbol size odd so that its center is a single pixel. */ + newSize |= 0x01; + return newSize; +} + +/* + *--------------------------------------------------------------------------- + * + * GetScreenPoints -- + * + * Generates a coordinate array of transformed screen coordinates from + * the data points. Coordinates with Inf, -Inf, or NaN values are + * removed. + * + * Results: + * The transformed screen coordinates are returned. + * + * Side effects: + * Memory is allocated for the coordinate array. + * + * + * Future ideas: + * Allow bad values to be removed (as done currently) or break + * into separate traces. Smoothing would be affected. + * + *--------------------------------------------------------------------------- + */ +static void +GetScreenPoints(Graph *graphPtr, LineElement *elemPtr, MapInfo *mapPtr) +{ + double *x, *y; + int i, np; + int count; + Point2d *points; + int *map; + + np = NUMBEROFPOINTS(elemPtr); + x = elemPtr->x.values; + y = elemPtr->y.values; + points = Blt_AssertMalloc(sizeof(Point2d) * np); + map = Blt_AssertMalloc(sizeof(int) * np); + + count = 0; /* Count the valid screen coordinates */ + if (graphPtr->inverted) { + for (i = 0; i < np; i++) { + if ((FINITE(x[i])) && (FINITE(y[i]))) { + points[count].x = Blt_HMap(elemPtr->axes.y, y[i]); + points[count].y = Blt_VMap(elemPtr->axes.x, x[i]); + map[count] = i; + count++; + } + } + } else { + for (i = 0; i < np; i++) { + if ((FINITE(x[i])) && (FINITE(y[i]))) { + points[count].x = Blt_HMap(elemPtr->axes.x, x[i]); + points[count].y = Blt_VMap(elemPtr->axes.y, y[i]); + map[count] = i; + count++; + } + } + } + mapPtr->screenPts = points; + mapPtr->nScreenPts = count; + mapPtr->map = map; +} + +/* + *--------------------------------------------------------------------------- + * + * ReducePoints -- + * + * Generates a coordinate array of transformed screen coordinates from + * the data points. + * + * Results: + * The transformed screen coordinates are returned. + * + * Side effects: + * Memory is allocated for the coordinate array. + * + *--------------------------------------------------------------------------- + */ +static void +ReducePoints(MapInfo *mapPtr, double tolerance) +{ + int i, np; + Point2d *screenPts; + int *map, *simple; + + simple = Blt_AssertMalloc(mapPtr->nScreenPts * sizeof(int)); + map = Blt_AssertMalloc(mapPtr->nScreenPts * sizeof(int)); + screenPts = Blt_AssertMalloc(mapPtr->nScreenPts * sizeof(Point2d)); + np = Blt_SimplifyLine(mapPtr->screenPts, 0, mapPtr->nScreenPts - 1, + tolerance, simple); + for (i = 0; i < np; i++) { + int k; + + k = simple[i]; + screenPts[i] = mapPtr->screenPts[k]; + map[i] = mapPtr->map[k]; + } +#ifdef notdef + if (np < mapPtr->nScreenPts) { + fprintf(stderr, "reduced from %d to %d\n", mapPtr->nScreenPts, np); + } +#endif + Blt_Free(mapPtr->screenPts); + Blt_Free(mapPtr->map); + Blt_Free(simple); + mapPtr->screenPts = screenPts; + mapPtr->map = map; + mapPtr->nScreenPts = np; +} + +/* + *--------------------------------------------------------------------------- + * + * GenerateSteps -- + * + * Resets the coordinate and pen index arrays adding new points for + * step-and-hold type smoothing. + * + * Results: + * None. + * + * Side Effects: + * The temporary arrays for screen coordinates and pen indices + * are updated. + * + *--------------------------------------------------------------------------- + */ +static void +GenerateSteps(MapInfo *mapPtr) +{ + int newSize; + int i, count; + Point2d *screenPts; + int *map; + + newSize = ((mapPtr->nScreenPts - 1) * 2) + 1; + screenPts = Blt_AssertMalloc(newSize * sizeof(Point2d)); + map = Blt_AssertMalloc(sizeof(int) * newSize); + screenPts[0] = mapPtr->screenPts[0]; + map[0] = 0; + + count = 1; + for (i = 1; i < mapPtr->nScreenPts; i++) { + screenPts[count + 1] = mapPtr->screenPts[i]; + + /* Hold last y-coordinate, use new x-coordinate */ + screenPts[count].x = screenPts[count + 1].x; + screenPts[count].y = screenPts[count - 1].y; + + /* Use the same style for both the hold and the step points */ + map[count] = map[count + 1] = mapPtr->map[i]; + count += 2; + } + Blt_Free(mapPtr->screenPts); + Blt_Free(mapPtr->map); + mapPtr->map = map; + mapPtr->screenPts = screenPts; + mapPtr->nScreenPts = newSize; +} + +/* + *--------------------------------------------------------------------------- + * + * GenerateSpline -- + * + * Computes a spline based upon the data points, returning a new (larger) + * coordinate array or points. + * + * Results: + * None. + * + * Side Effects: + * The temporary arrays for screen coordinates and data map are updated + * based upon spline. + * + * FIXME: Can't interpolate knots along the Y-axis. Need to break + * up point array into interchangable X and Y vectors earlier. + * Pass extents (left/right or top/bottom) as parameters. + * + *--------------------------------------------------------------------------- + */ +static void +GenerateSpline(Graph *graphPtr, LineElement *elemPtr, MapInfo *mapPtr) +{ + Point2d *origPts, *iPts; + int *map; + int extra; + int niPts, nOrigPts; + int result; + int i, j, count; + + nOrigPts = mapPtr->nScreenPts; + origPts = mapPtr->screenPts; + assert(mapPtr->nScreenPts > 0); + for (i = 0, j = 1; j < nOrigPts; i++, j++) { + if (origPts[j].x <= origPts[i].x) { + return; /* Points are not monotonically + * increasing */ + } + } + if (((origPts[0].x > (double)graphPtr->right)) || + ((origPts[mapPtr->nScreenPts - 1].x < (double)graphPtr->left))) { + return; /* All points are clipped */ + } + + /* + * The spline is computed in screen coordinates instead of data points so + * that we can select the abscissas of the interpolated points from each + * pixel horizontally across the plotting area. + */ + extra = (graphPtr->right - graphPtr->left) + 1; + if (extra < 1) { + return; + } + niPts = nOrigPts + extra + 1; + iPts = Blt_AssertMalloc(niPts * sizeof(Point2d)); + map = Blt_AssertMalloc(sizeof(int) * niPts); + /* Populate the x2 array with both the original X-coordinates and extra + * X-coordinates for each horizontal pixel that the line segment + * contains. */ + count = 0; + for (i = 0, j = 1; j < nOrigPts; i++, j++) { + + /* Add the original x-coordinate */ + iPts[count].x = origPts[i].x; + + /* Include the starting offset of the point in the offset array */ + map[count] = mapPtr->map[i]; + count++; + + /* Is any part of the interval (line segment) in the plotting area? */ + if ((origPts[j].x >= (double)graphPtr->left) || + (origPts[i].x <= (double)graphPtr->right)) { + double x, last; + + x = origPts[i].x + 1.0; + + /* + * Since the line segment may be partially clipped on the left or + * right side, the points to interpolate are always interior to + * the plotting area. + * + * left right + * x1----|---------------------------|---x2 + * + * Pick the max of the starting X-coordinate and the left edge and + * the min of the last X-coordinate and the right edge. + */ + x = MAX(x, (double)graphPtr->left); + last = MIN(origPts[j].x, (double)graphPtr->right); + + /* Add the extra x-coordinates to the interval. */ + while (x < last) { + map[count] = mapPtr->map[i]; + iPts[count++].x = x; + x++; + } + } + } + niPts = count; + result = FALSE; + if (elemPtr->smooth == PEN_SMOOTH_NATURAL) { + result = Blt_NaturalSpline(origPts, nOrigPts, iPts, niPts); + } else if (elemPtr->smooth == PEN_SMOOTH_QUADRATIC) { + result = Blt_QuadraticSpline(origPts, nOrigPts, iPts, niPts); + } + if (!result) { + /* The spline interpolation failed. We'll fallback to the current + * coordinates and do no smoothing (standard line segments). */ + elemPtr->smooth = PEN_SMOOTH_LINEAR; + Blt_Free(iPts); + Blt_Free(map); + } else { + Blt_Free(mapPtr->screenPts); + Blt_Free(mapPtr->map); + mapPtr->map = map; + mapPtr->screenPts = iPts; + mapPtr->nScreenPts = niPts; + } +} + + +/* + *--------------------------------------------------------------------------- + * + * GenerateParametricSpline -- + * + * Computes a spline based upon the data points, returning a new (larger) + * coordinate array or points. + * + * Results: + * None. + * + * Side Effects: + * The temporary arrays for screen coordinates and data map are updated + * based upon spline. + * + * FIXME: Can't interpolate knots along the Y-axis. Need to break + * up point array into interchangable X and Y vectors earlier. + * Pass extents (left/right or top/bottom) as parameters. + * + *--------------------------------------------------------------------------- + */ +static void +GenerateParametricSpline(Graph *graphPtr, LineElement *elemPtr, MapInfo *mapPtr) +{ + Region2d exts; + Point2d *origPts, *iPts; + int *map; + int niPts, nOrigPts; + int result; + int i, j, count; + + nOrigPts = mapPtr->nScreenPts; + origPts = mapPtr->screenPts; + assert(mapPtr->nScreenPts > 0); + + Blt_GraphExtents(graphPtr, &exts); + + /* + * Populate the x2 array with both the original X-coordinates and extra + * X-coordinates for each horizontal pixel that the line segment contains. + */ + count = 1; + for (i = 0, j = 1; j < nOrigPts; i++, j++) { + Point2d p, q; + + p = origPts[i]; + q = origPts[j]; + count++; + if (Blt_LineRectClip(&exts, &p, &q)) { + count += (int)(hypot(q.x - p.x, q.y - p.y) * 0.5); + } + } + niPts = count; + iPts = Blt_AssertMalloc(niPts * sizeof(Point2d)); + map = Blt_AssertMalloc(sizeof(int) * niPts); + + /* + * FIXME: This is just plain wrong. The spline should be computed + * and evaluated in separate steps. This will mean breaking + * up this routine since the catrom coefficients can be + * independently computed for original data point. This + * also handles the problem of allocating enough points + * since evaluation is independent of the number of points + * to be evalualted. The interpolated + * line segments should be clipped, not the original segments. + */ + count = 0; + for (i = 0, j = 1; j < nOrigPts; i++, j++) { + Point2d p, q; + double d; + + p = origPts[i]; + q = origPts[j]; + + d = hypot(q.x - p.x, q.y - p.y); + /* Add the original x-coordinate */ + iPts[count].x = (double)i; + iPts[count].y = 0.0; + + /* Include the starting offset of the point in the offset array */ + map[count] = mapPtr->map[i]; + count++; + + /* Is any part of the interval (line segment) in the plotting + * area? */ + + if (Blt_LineRectClip(&exts, &p, &q)) { + double dp, dq; + + /* Distance of original point to p. */ + dp = hypot(p.x - origPts[i].x, p.y - origPts[i].y); + /* Distance of original point to q. */ + dq = hypot(q.x - origPts[i].x, q.y - origPts[i].y); + dp += 2.0; + while(dp <= dq) { + /* Point is indicated by its interval and parameter t. */ + iPts[count].x = (double)i; + iPts[count].y = dp / d; + map[count] = mapPtr->map[i]; + count++; + dp += 2.0; + } + } + } + iPts[count].x = (double)i; + iPts[count].y = 0.0; + map[count] = mapPtr->map[i]; + count++; + niPts = count; + result = FALSE; + if (elemPtr->smooth == PEN_SMOOTH_NATURAL) { + result = Blt_NaturalParametricSpline(origPts, nOrigPts, &exts, FALSE, + iPts, niPts); + } else if (elemPtr->smooth == PEN_SMOOTH_CATROM) { + result = Blt_CatromParametricSpline(origPts, nOrigPts, iPts, niPts); + } + if (!result) { + /* The spline interpolation failed. We will fall back to the current + * coordinates and do no smoothing (standard line segments). */ + elemPtr->smooth = PEN_SMOOTH_LINEAR; + Blt_Free(iPts); + Blt_Free(map); + } else { + Blt_Free(mapPtr->screenPts); + Blt_Free(mapPtr->map); + mapPtr->map = map; + mapPtr->screenPts = iPts; + mapPtr->nScreenPts = niPts; + } +} + + +/* + *--------------------------------------------------------------------------- + * + * MapSymbols -- + * + * Creates two arrays of points and pen map, filled with the screen + * coordinates of the visible + * + * Results: + * None. + * + * Side effects: + * Memory is freed and allocated for the index array. + * + *--------------------------------------------------------------------------- + */ +static void +MapSymbols(Graph *graphPtr, LineElement *elemPtr, MapInfo *mapPtr) +{ + Region2d exts; + Point2d *pp, *points; + int *map; + int i, count; + + points = Blt_AssertMalloc(sizeof(Point2d) * mapPtr->nScreenPts); + map = Blt_AssertMalloc(sizeof(int) * mapPtr->nScreenPts); + + Blt_GraphExtents(graphPtr, &exts); + count = 0; /* Count the number of visible points */ + + for (pp = mapPtr->screenPts, i = 0; i < mapPtr->nScreenPts; i++, pp++) { + if (PointInRegion(&exts, pp->x, pp->y)) { + points[count].x = pp->x; + points[count].y = pp->y; + map[count] = mapPtr->map[i]; + count++; + } + } + elemPtr->symbolPts.points = points; + elemPtr->symbolPts.length = count; + elemPtr->symbolPts.map = map; +} + +/* + *--------------------------------------------------------------------------- + * + * MapActiveSymbols -- + * + * Creates an array of points of the active graph coordinates. + * + * Results: + * None. + * + * Side effects: + * Memory is freed and allocated for the active point array. + * + *--------------------------------------------------------------------------- + */ +static void +MapActiveSymbols(Graph *graphPtr, LineElement *elemPtr) +{ + Point2d *points; + Region2d exts; + int *map; + int count, i, np; + + if (elemPtr->activePts.points != NULL) { + Blt_Free(elemPtr->activePts.points); + elemPtr->activePts.points = NULL; + } + if (elemPtr->activePts.map != NULL) { + Blt_Free(elemPtr->activePts.map); + elemPtr->activePts.map = NULL; + } + Blt_GraphExtents(graphPtr, &exts); + points = Blt_AssertMalloc(sizeof(Point2d) * elemPtr->nActiveIndices); + map = Blt_AssertMalloc(sizeof(int) * elemPtr->nActiveIndices); + np = NUMBEROFPOINTS(elemPtr); + count = 0; /* Count the visible active points */ + for (i = 0; i < elemPtr->nActiveIndices; i++) { + double x, y; + int iPoint; + + iPoint = elemPtr->activeIndices[i]; + if (iPoint >= np) { + continue; /* Index not available */ + } + x = elemPtr->x.values[iPoint]; + y = elemPtr->y.values[iPoint]; + points[count] = Blt_Map2D(graphPtr, x, y, &elemPtr->axes); + map[count] = iPoint; + if (PointInRegion(&exts, points[count].x, points[count].y)) { + count++; + } + } + if (count > 0) { + elemPtr->activePts.points = points; + elemPtr->activePts.map = map; + } else { + /* No active points were visible. */ + Blt_Free(points); + Blt_Free(map); + } + elemPtr->activePts.length = count; + elemPtr->flags &= ~ACTIVE_PENDING; +} + +/* + *--------------------------------------------------------------------------- + * + * MapStrip -- + * + * Creates an array of line segments of the graph coordinates. + * + * Results: + * None. + * + * Side effects: + * Memory is allocated for the line segment array. + * + *--------------------------------------------------------------------------- + */ +static void +MapStrip(Graph *graphPtr, LineElement *elemPtr, MapInfo *mapPtr) +{ + Region2d exts; + Segment2d *lines; + int *indices, *indexPtr; + Point2d *pend, *pp; + Segment2d *sp; + int count; + + indices = Blt_AssertMalloc(sizeof(int) * mapPtr->nScreenPts); + + /* + * Create array to hold points for line segments (not polyline + * coordinates). So allocate twice the number of points. + */ + sp = lines = Blt_AssertMalloc(mapPtr->nScreenPts * sizeof(Segment2d)); + Blt_GraphExtents(graphPtr, &exts); + count = 0; /* Count the number of segments. */ + indexPtr = mapPtr->map; + for (pp = mapPtr->screenPts, pend = pp + (mapPtr->nScreenPts - 1); + pp < pend; pp++, indexPtr++) { + sp->p = pp[0], sp->q = pp[1]; + if (Blt_LineRectClip(&exts, &sp->p, &sp->q)) { + sp++; + indices[count] = *indexPtr; + count++; + } + } + elemPtr->lines.map = indices; + elemPtr->lines.length = count; + elemPtr->lines.segments = lines; +} + +/* + *--------------------------------------------------------------------------- + * + * MergePens -- + * + * Reorders the both arrays of points and segments to merge pens. + * + * Results: + * None. + * + * Side effects: + * The old arrays are freed and new ones allocated containing + * the reordered points and segments. + * + *--------------------------------------------------------------------------- + */ +static void +MergePens(LineElement *elemPtr, LineStyle **styleMap) +{ + if (Blt_Chain_GetLength(elemPtr->styles) < 2) { + Blt_ChainLink link; + LineStyle *stylePtr; + + link = Blt_Chain_FirstLink(elemPtr->styles); + stylePtr = Blt_Chain_GetValue(link); + stylePtr->errorBarCapWidth = elemPtr->errorBarCapWidth; + stylePtr->lines.length = elemPtr->lines.length; + stylePtr->lines.segments = elemPtr->lines.segments; + stylePtr->symbolPts.length = elemPtr->symbolPts.length; + stylePtr->symbolPts.points = elemPtr->symbolPts.points; + stylePtr->xeb.length = elemPtr->xeb.length; + stylePtr->xeb.segments = elemPtr->xeb.segments; + stylePtr->yeb.length = elemPtr->yeb.length; + stylePtr->yeb.segments = elemPtr->yeb.segments; + return; + } + + /* We have more than one style. Group line segments and points of like pen + * styles. */ + if (elemPtr->lines.length > 0) { + Blt_ChainLink link; + Segment2d *sp, *segments; + int *ip; + int *map; + + segments = Blt_AssertMalloc(elemPtr->lines.length * sizeof(Segment2d)); + map = Blt_AssertMalloc(elemPtr->lines.length * sizeof(int)); + sp = segments, ip = map; + for (link = Blt_Chain_FirstLink(elemPtr->styles); + link != NULL; link = Blt_Chain_NextLink(link)) { + LineStyle *stylePtr; + int i; + + stylePtr = Blt_Chain_GetValue(link); + stylePtr->lines.segments = sp; + for (i = 0; i < elemPtr->lines.length; i++) { + int iData; + + iData = elemPtr->lines.map[i]; + if (styleMap[iData] == stylePtr) { + *sp++ = elemPtr->lines.segments[i]; + *ip++ = iData; + } + } + stylePtr->lines.length = sp - stylePtr->lines.segments; + } + Blt_Free(elemPtr->lines.segments); + elemPtr->lines.segments = segments; + Blt_Free(elemPtr->lines.map); + elemPtr->lines.map = map; + } + if (elemPtr->symbolPts.length > 0) { + Blt_ChainLink link; + int *ip; + Point2d *points, *pp; + int *map; + + points = Blt_AssertMalloc(elemPtr->symbolPts.length * sizeof(Point2d)); + map = Blt_AssertMalloc(elemPtr->symbolPts.length * sizeof(int)); + pp = points, ip = map; + for (link = Blt_Chain_FirstLink(elemPtr->styles); + link != NULL; link = Blt_Chain_NextLink(link)) { + LineStyle *stylePtr; + int i; + + stylePtr = Blt_Chain_GetValue(link); + stylePtr->symbolPts.points = pp; + for (i = 0; i < elemPtr->symbolPts.length; i++) { + int iData; + + iData = elemPtr->symbolPts.map[i]; + if (styleMap[iData] == stylePtr) { + *pp++ = elemPtr->symbolPts.points[i]; + *ip++ = iData; + } + } + stylePtr->symbolPts.length = pp - stylePtr->symbolPts.points; + } + Blt_Free(elemPtr->symbolPts.points); + Blt_Free(elemPtr->symbolPts.map); + elemPtr->symbolPts.points = points; + elemPtr->symbolPts.map = map; + } + if (elemPtr->xeb.length > 0) { + Segment2d *segments, *sp; + int *map, *ip; + Blt_ChainLink link; + + segments = Blt_AssertMalloc(elemPtr->xeb.length * sizeof(Segment2d)); + map = Blt_AssertMalloc(elemPtr->xeb.length * sizeof(int)); + sp = segments, ip = map; + for (link = Blt_Chain_FirstLink(elemPtr->styles); + link != NULL; link = Blt_Chain_NextLink(link)) { + LineStyle *stylePtr; + int i; + + stylePtr = Blt_Chain_GetValue(link); + stylePtr->xeb.segments = sp; + for (i = 0; i < elemPtr->xeb.length; i++) { + int iData; + + iData = elemPtr->xeb.map[i]; + if (styleMap[iData] == stylePtr) { + *sp++ = elemPtr->xeb.segments[i]; + *ip++ = iData; + } + } + stylePtr->xeb.length = sp - stylePtr->xeb.segments; + } + Blt_Free(elemPtr->xeb.segments); + Blt_Free(elemPtr->xeb.map); + elemPtr->xeb.segments = segments; + elemPtr->xeb.map = map; + } + if (elemPtr->yeb.length > 0) { + Segment2d *segments, *sp; + int *map, *ip; + Blt_ChainLink link; + + segments = Blt_AssertMalloc(elemPtr->yeb.length * sizeof(Segment2d)); + map = Blt_AssertMalloc(elemPtr->yeb.length * sizeof(int)); + sp = segments, ip = map; + for (link = Blt_Chain_FirstLink(elemPtr->styles); + link != NULL; link = Blt_Chain_NextLink(link)) { + LineStyle *stylePtr; + int i; + + stylePtr = Blt_Chain_GetValue(link); + stylePtr->yeb.segments = sp; + for (i = 0; i < elemPtr->yeb.length; i++) { + int iData; + + iData = elemPtr->yeb.map[i]; + if (styleMap[iData] == stylePtr) { + *sp++ = elemPtr->yeb.segments[i]; + *ip++ = iData; + } + } + stylePtr->yeb.length = sp - stylePtr->yeb.segments; + } + Blt_Free(elemPtr->yeb.segments); + elemPtr->yeb.segments = segments; + Blt_Free(elemPtr->yeb.map); + elemPtr->yeb.map = map; + } +} + +#define CLIP_TOP (1<<0) +#define CLIP_BOTTOM (1<<1) +#define CLIP_RIGHT (1<<2) +#define CLIP_LEFT (1<<3) + +INLINE static int +OutCode(Region2d *extsPtr, Point2d *p) +{ + int code; + + code = 0; + if (p->x > extsPtr->right) { + code |= CLIP_RIGHT; + } else if (p->x < extsPtr->left) { + code |= CLIP_LEFT; + } + if (p->y > extsPtr->bottom) { + code |= CLIP_BOTTOM; + } else if (p->y < extsPtr->top) { + code |= CLIP_TOP; + } + return code; +} + +static int +ClipSegment( + Region2d *extsPtr, + int code1, int code2, + Point2d *p, Point2d *q) +{ + int inside, outside; + + inside = ((code1 | code2) == 0); + outside = ((code1 & code2) != 0); + + /* + * In the worst case, we'll clip the line segment against each of the four + * sides of the bounding rectangle. + */ + while ((!outside) && (!inside)) { + if (code1 == 0) { + Point2d *tmp; + int code; + + /* Swap pointers and out codes */ + tmp = p, p = q, q = tmp; + code = code1, code1 = code2, code2 = code; + } + if (code1 & CLIP_LEFT) { + p->y += (q->y - p->y) * + (extsPtr->left - p->x) / (q->x - p->x); + p->x = extsPtr->left; + } else if (code1 & CLIP_RIGHT) { + p->y += (q->y - p->y) * + (extsPtr->right - p->x) / (q->x - p->x); + p->x = extsPtr->right; + } else if (code1 & CLIP_BOTTOM) { + p->x += (q->x - p->x) * + (extsPtr->bottom - p->y) / (q->y - p->y); + p->y = extsPtr->bottom; + } else if (code1 & CLIP_TOP) { + p->x += (q->x - p->x) * + (extsPtr->top - p->y) / (q->y - p->y); + p->y = extsPtr->top; + } + code1 = OutCode(extsPtr, p); + + inside = ((code1 | code2) == 0); + outside = ((code1 & code2) != 0); + } + return (!inside); +} + +/* + *--------------------------------------------------------------------------- + * + * SaveTrace -- + * + * Creates a new trace and inserts it into the line's list of traces. + * + * Results: + * None. + * + *--------------------------------------------------------------------------- + */ +static void +SaveTrace( + LineElement *elemPtr, + int start, /* Starting index of the trace in data point + * array. Used to figure out closest point */ + int length, /* Number of points forming the trace */ + MapInfo *mapPtr) +{ + Trace *tracePtr; + Point2d *screenPts; + int *map; + int i, j; + + tracePtr = Blt_AssertMalloc(sizeof(Trace)); + screenPts = Blt_AssertMalloc(sizeof(Point2d) * length); + map = Blt_AssertMalloc(sizeof(int) * length); + + /* Copy the screen coordinates of the trace into the point array */ + + if (mapPtr->map != NULL) { + for (i = 0, j = start; i < length; i++, j++) { + screenPts[i].x = mapPtr->screenPts[j].x; + screenPts[i].y = mapPtr->screenPts[j].y; + map[i] = mapPtr->map[j]; + } + } else { + for (i = 0, j = start; i < length; i++, j++) { + screenPts[i].x = mapPtr->screenPts[j].x; + screenPts[i].y = mapPtr->screenPts[j].y; + map[i] = j; + } + } + tracePtr->screenPts.length = length; + tracePtr->screenPts.points = screenPts; + tracePtr->screenPts.map = map; + tracePtr->start = start; + if (elemPtr->traces == NULL) { + elemPtr->traces = Blt_Chain_Create(); + } + Blt_Chain_Append(elemPtr->traces, tracePtr); +} + +/* + *--------------------------------------------------------------------------- + * + * FreeTraces -- + * + * Deletes all the traces for the line. + * + * Results: + * None. + * + *--------------------------------------------------------------------------- + */ +static void +FreeTraces(LineElement *elemPtr) +{ + Blt_ChainLink link; + + for (link = Blt_Chain_FirstLink(elemPtr->traces); link != NULL; + link = Blt_Chain_NextLink(link)) { + Trace *tracePtr; + + tracePtr = Blt_Chain_GetValue(link); + Blt_Free(tracePtr->screenPts.map); + Blt_Free(tracePtr->screenPts.points); + Blt_Free(tracePtr); + } + Blt_Chain_Destroy(elemPtr->traces); + elemPtr->traces = NULL; +} + +/* + *--------------------------------------------------------------------------- + * + * MapTraces -- + * + * Creates an array of line segments of the graph coordinates. + * + * Results: + * None. + * + * Side effects: + * Memory is allocated for the line segment array. + * + *--------------------------------------------------------------------------- + */ +static void +MapTraces(Graph *graphPtr, LineElement *elemPtr, MapInfo *mapPtr) +{ + Point2d *p, *q; + Region2d exts; + int code1; + int i; + int start, count; + + Blt_GraphExtents(graphPtr, &exts); + count = 1; + code1 = OutCode(&exts, mapPtr->screenPts); + p = mapPtr->screenPts; + q = p + 1; + for (i = 1; i < mapPtr->nScreenPts; i++, p++, q++) { + Point2d s; + int code2; + int broken, offscreen; + + s.x = s.y = 0; + code2 = OutCode(&exts, q); + if (code2 != 0) { + /* Save the coordinates of the last point, before clipping */ + s = *q; + } + broken = BROKEN_TRACE(elemPtr->penDir, p->x, q->x); + offscreen = ClipSegment(&exts, code1, code2, p, q); + if (broken || offscreen) { + + /* + * The last line segment is either totally clipped by the plotting + * area or the x-direction is wrong, breaking the trace. Either + * way, save information about the last trace (if one exists), + * discarding the current line segment + */ + + if (count > 1) { + start = i - count; + SaveTrace(elemPtr, start, count, mapPtr); + count = 1; + } + } else { + count++; /* Add the point to the trace. */ + if (code2 != 0) { + + /* + * If the last point is clipped, this means that the trace is + * broken after this point. Restore the original coordinate + * (before clipping) after saving the trace. + */ + + start = i - (count - 1); + SaveTrace(elemPtr, start, count, mapPtr); + mapPtr->screenPts[i] = s; + count = 1; + } + } + code1 = code2; + } + if (count > 1) { + start = i - count; + SaveTrace(elemPtr, start, count, mapPtr); + } +} + +/* + *--------------------------------------------------------------------------- + * + * MapFillArea -- + * + * Creates an array of points that represent a polygon that fills + * the area under the element. + * + * Results: + * None. + * + * Side effects: + * Memory is allocated for the polygon point array. + * + *--------------------------------------------------------------------------- + */ +static void +MapFillArea(Graph *graphPtr, LineElement *elemPtr, MapInfo *mapPtr) +{ + Point2d *origPts, *clipPts; + Region2d exts; + int np; + + if (elemPtr->fillPts != NULL) { + Blt_Free(elemPtr->fillPts); + elemPtr->fillPts = NULL; + elemPtr->nFillPts = 0; + } + if (mapPtr->nScreenPts < 3) { + return; + } + np = mapPtr->nScreenPts + 3; + Blt_GraphExtents(graphPtr, &exts); + + origPts = Blt_AssertMalloc(sizeof(Point2d) * np); + if (graphPtr->inverted) { + double minX; + int i; + + minX = (double)elemPtr->axes.y->screenMin; + for (i = 0; i < mapPtr->nScreenPts; i++) { + origPts[i].x = mapPtr->screenPts[i].x + 1; + origPts[i].y = mapPtr->screenPts[i].y; + if (origPts[i].x < minX) { + minX = origPts[i].x; + } + } + /* Add edges to make (if necessary) the polygon fill to the bottom of + * plotting window */ + origPts[i].x = minX; + origPts[i].y = origPts[i - 1].y; + i++; + origPts[i].x = minX; + origPts[i].y = origPts[0].y; + i++; + origPts[i] = origPts[0]; + } else { + double maxY; + int i; + + maxY = (double)elemPtr->axes.y->bottom; + for (i = 0; i < mapPtr->nScreenPts; i++) { + origPts[i].x = mapPtr->screenPts[i].x + 1; + origPts[i].y = mapPtr->screenPts[i].y; + if (origPts[i].y > maxY) { + maxY = origPts[i].y; + } + } + /* Add edges to extend the fill polygon to the bottom of plotting + * window */ + origPts[i].x = origPts[i - 1].x; + origPts[i].y = maxY; + i++; + origPts[i].x = origPts[0].x; + origPts[i].y = maxY; + i++; + origPts[i] = origPts[0]; + } + + clipPts = Blt_AssertMalloc(sizeof(Point2d) * np * 3); + np = Blt_PolyRectClip(&exts, origPts, np - 1, clipPts); + + Blt_Free(origPts); + if (np < 3) { + Blt_Free(clipPts); + } else { + elemPtr->fillPts = clipPts; + elemPtr->nFillPts = np; + } +} + +static void +ResetLine(LineElement *elemPtr) +{ + FreeTraces(elemPtr); + ResetStylePalette(elemPtr->styles); + if (elemPtr->symbolPts.points != NULL) { + Blt_Free(elemPtr->symbolPts.points); + } + if (elemPtr->symbolPts.map != NULL) { + Blt_Free(elemPtr->symbolPts.map); + } + if (elemPtr->lines.segments != NULL) { + Blt_Free(elemPtr->lines.segments); + } + if (elemPtr->lines.map != NULL) { + Blt_Free(elemPtr->lines.map); + } + if (elemPtr->activePts.points != NULL) { + Blt_Free(elemPtr->activePts.points); + } + if (elemPtr->activePts.map != NULL) { + Blt_Free(elemPtr->activePts.map); + } + if (elemPtr->xeb.segments != NULL) { + Blt_Free(elemPtr->xeb.segments); + } + if (elemPtr->xeb.map != NULL) { + Blt_Free(elemPtr->xeb.map); + } + if (elemPtr->yeb.segments != NULL) { + Blt_Free(elemPtr->yeb.segments); + } + if (elemPtr->yeb.map != NULL) { + Blt_Free(elemPtr->yeb.map); + } + elemPtr->xeb.segments = elemPtr->yeb.segments = elemPtr->lines.segments = NULL; + elemPtr->symbolPts.points = elemPtr->activePts.points = NULL; + elemPtr->lines.map = elemPtr->symbolPts.map = elemPtr->xeb.map = + elemPtr->yeb.map = elemPtr->activePts.map = NULL; + elemPtr->activePts.length = elemPtr->symbolPts.length = + elemPtr->lines.length = elemPtr->xeb.length = elemPtr->yeb.length = 0; +} + +/* + *--------------------------------------------------------------------------- + * + * MapErrorBars -- + * + * Creates two arrays of points and pen indices, filled with the screen + * coordinates of the visible + * + * Results: + * None. + * + * Side effects: + * Memory is freed and allocated for the index array. + * + *--------------------------------------------------------------------------- + */ +static void +MapErrorBars(Graph *graphPtr, LineElement *elemPtr, LineStyle **styleMap) +{ + int n, np; + Region2d exts; + + Blt_GraphExtents(graphPtr, &exts); + np = NUMBEROFPOINTS(elemPtr); + if (elemPtr->xError.nValues > 0) { + n = MIN(elemPtr->xError.nValues, np); + } else { + n = MIN3(elemPtr->xHigh.nValues, elemPtr->xLow.nValues, np); + } + if (n > 0) { + Segment2d *errorBars; + Segment2d *segPtr; + int *errorToData; + int *indexPtr; + int i; + + segPtr = errorBars = Blt_AssertMalloc(n * 3 * sizeof(Segment2d)); + indexPtr = errorToData = Blt_AssertMalloc(n * 3 * sizeof(int)); + for (i = 0; i < n; i++) { + double x, y; + double high, low; + LineStyle *stylePtr; + + x = elemPtr->x.values[i]; + y = elemPtr->y.values[i]; + stylePtr = styleMap[i]; + if ((FINITE(x)) && (FINITE(y))) { + if (elemPtr->xError.nValues > 0) { + high = x + elemPtr->xError.values[i]; + low = x - elemPtr->xError.values[i]; + } else { + high = elemPtr->xHigh.values[i]; + low = elemPtr->xLow.values[i]; + } + if ((FINITE(high)) && (FINITE(low))) { + Point2d p, q; + + p = Blt_Map2D(graphPtr, high, y, &elemPtr->axes); + q = Blt_Map2D(graphPtr, low, y, &elemPtr->axes); + segPtr->p = p; + segPtr->q = q; + if (Blt_LineRectClip(&exts, &segPtr->p, &segPtr->q)) { + segPtr++; + *indexPtr++ = i; + } + /* Left cap */ + segPtr->p.x = segPtr->q.x = p.x; + segPtr->p.y = p.y - stylePtr->errorBarCapWidth; + segPtr->q.y = p.y + stylePtr->errorBarCapWidth; + if (Blt_LineRectClip(&exts, &segPtr->p, &segPtr->q)) { + segPtr++; + *indexPtr++ = i; + } + /* Right cap */ + segPtr->p.x = segPtr->q.x = q.x; + segPtr->p.y = q.y - stylePtr->errorBarCapWidth; + segPtr->q.y = q.y + stylePtr->errorBarCapWidth; + if (Blt_LineRectClip(&exts, &segPtr->p, &segPtr->q)) { + segPtr++; + *indexPtr++ = i; + } + } + } + } + elemPtr->xeb.segments = errorBars; + elemPtr->xeb.length = segPtr - errorBars; + elemPtr->xeb.map = errorToData; + } + if (elemPtr->yError.nValues > 0) { + n = MIN(elemPtr->yError.nValues, np); + } else { + n = MIN3(elemPtr->yHigh.nValues, elemPtr->yLow.nValues, np); + } + if (n > 0) { + Segment2d *errorBars; + Segment2d *segPtr; + int *errorToData; + int *indexPtr; + int i; + + segPtr = errorBars = Blt_AssertMalloc(n * 3 * sizeof(Segment2d)); + indexPtr = errorToData = Blt_AssertMalloc(n * 3 * sizeof(int)); + for (i = 0; i < n; i++) { + double x, y; + double high, low; + LineStyle *stylePtr; + + x = elemPtr->x.values[i]; + y = elemPtr->y.values[i]; + stylePtr = styleMap[i]; + if ((FINITE(x)) && (FINITE(y))) { + if (elemPtr->yError.nValues > 0) { + high = y + elemPtr->yError.values[i]; + low = y - elemPtr->yError.values[i]; + } else { + high = elemPtr->yHigh.values[i]; + low = elemPtr->yLow.values[i]; + } + if ((FINITE(high)) && (FINITE(low))) { + Point2d p, q; + + p = Blt_Map2D(graphPtr, x, high, &elemPtr->axes); + q = Blt_Map2D(graphPtr, x, low, &elemPtr->axes); + segPtr->p = p; + segPtr->q = q; + if (Blt_LineRectClip(&exts, &segPtr->p, &segPtr->q)) { + segPtr++; + *indexPtr++ = i; + } + /* Top cap. */ + segPtr->p.y = segPtr->q.y = p.y; + segPtr->p.x = p.x - stylePtr->errorBarCapWidth; + segPtr->q.x = p.x + stylePtr->errorBarCapWidth; + if (Blt_LineRectClip(&exts, &segPtr->p, &segPtr->q)) { + segPtr++; + *indexPtr++ = i; + } + /* Bottom cap. */ + segPtr->p.y = segPtr->q.y = q.y; + segPtr->p.x = q.x - stylePtr->errorBarCapWidth; + segPtr->q.x = q.x + stylePtr->errorBarCapWidth; + if (Blt_LineRectClip(&exts, &segPtr->p, &segPtr->q)) { + segPtr++; + *indexPtr++ = i; + } + } + } + } + elemPtr->yeb.segments = errorBars; + elemPtr->yeb.length = segPtr - errorBars; + elemPtr->yeb.map = errorToData; + } +} + +/* + *--------------------------------------------------------------------------- + * + * MapLineProc -- + * + * Calculates the actual window coordinates of the line element. The + * window coordinates are saved in an allocated point array. + * + * Results: + * None. + * + * Side effects: + * Memory is (re)allocated for the point array. + * + *--------------------------------------------------------------------------- + */ +static void +MapLineProc(Graph *graphPtr, Element *basePtr) +{ + LineElement *elemPtr = (LineElement *)basePtr; + MapInfo mi; + int size, np; + LineStyle **styleMap; + Blt_ChainLink link; + + ResetLine(elemPtr); + np = NUMBEROFPOINTS(elemPtr); + if (np < 1) { + return; /* No data points */ + } + GetScreenPoints(graphPtr, elemPtr, &mi); + MapSymbols(graphPtr, elemPtr, &mi); + + if ((elemPtr->flags & ACTIVE_PENDING) && (elemPtr->nActiveIndices > 0)) { + MapActiveSymbols(graphPtr, elemPtr); + } + /* + * Map connecting line segments if they are to be displayed. + */ + elemPtr->smooth = elemPtr->reqSmooth; + if ((np > 1) && ((graphPtr->classId == CID_ELEM_STRIP) || + (elemPtr->builtinPen.traceWidth > 0))) { + /* + * Do smoothing if necessary. This can extend the coordinate array, + * so both mi.points and mi.nPoints may change. + */ + switch (elemPtr->smooth) { + case PEN_SMOOTH_STEP: + GenerateSteps(&mi); + break; + + case PEN_SMOOTH_NATURAL: + case PEN_SMOOTH_QUADRATIC: + if (mi.nScreenPts < 3) { + /* Can't interpolate with less than three points. */ + elemPtr->smooth = PEN_SMOOTH_LINEAR; + } else { + GenerateSpline(graphPtr, elemPtr, &mi); + } + break; + + case PEN_SMOOTH_CATROM: + if (mi.nScreenPts < 3) { + /* Can't interpolate with less than three points. */ + elemPtr->smooth = PEN_SMOOTH_LINEAR; + } else { + GenerateParametricSpline(graphPtr, elemPtr, &mi); + } + break; + + default: + break; + } + if (elemPtr->rTolerance > 0.0) { + ReducePoints(&mi, elemPtr->rTolerance); + } + if (elemPtr->fillBg != NULL) { + MapFillArea(graphPtr, elemPtr, &mi); + } + if (graphPtr->classId == CID_ELEM_STRIP) { + MapStrip(graphPtr, elemPtr, &mi); + } else { + MapTraces(graphPtr, elemPtr, &mi); + } + } + Blt_Free(mi.screenPts); + Blt_Free(mi.map); + + /* Set the symbol size of all the pen styles. */ + for (link = Blt_Chain_FirstLink(elemPtr->styles); link != NULL; + link = Blt_Chain_NextLink(link)) { + LineStyle *stylePtr; + LinePen *penPtr; + + stylePtr = Blt_Chain_GetValue(link); + penPtr = (LinePen *)stylePtr->penPtr; + size = ScaleSymbol(elemPtr, penPtr->symbol.size); + stylePtr->symbolSize = size; + stylePtr->errorBarCapWidth = (penPtr->errorBarCapWidth > 0) + ? penPtr->errorBarCapWidth : Round(size * 0.6666666); + stylePtr->errorBarCapWidth /= 2; + } + styleMap = (LineStyle **)Blt_StyleMap((Element *)elemPtr); + if (((elemPtr->yHigh.nValues > 0) && (elemPtr->yLow.nValues > 0)) || + ((elemPtr->xHigh.nValues > 0) && (elemPtr->xLow.nValues > 0)) || + (elemPtr->xError.nValues > 0) || (elemPtr->yError.nValues > 0)) { + MapErrorBars(graphPtr, elemPtr, styleMap); + } + MergePens(elemPtr, styleMap); + Blt_Free(styleMap); +} + +static double +DistanceToLineProc( + int x, int y, /* Sample X-Y coordinate. */ + Point2d *p, Point2d *q, /* End points of the line segment. */ + Point2d *t) /* (out) Point on line segment. */ +{ + double right, left, top, bottom; + + *t = Blt_GetProjection(x, y, p, q); + if (p->x > q->x) { + right = p->x, left = q->x; + } else { + left = p->x, right = q->x; + } + if (p->y > q->y) { + bottom = p->y, top = q->y; + } else { + top = p->y, bottom = q->y; + } + if (t->x > right) { + t->x = right; + } else if (t->x < left) { + t->x = left; + } + if (t->y > bottom) { + t->y = bottom; + } else if (t->y < top) { + t->y = top; + } + return hypot((t->x - x), (t->y - y)); +} + +static double +DistanceToXProc( + int x, int y, /* Search X-Y coordinate. */ + Point2d *p, + Point2d *q, /* End points of the line segment. */ + Point2d *t) /* (out) Point on line segment. */ +{ + double dx, dy; + double d; + + if (p->x > q->x) { + if ((x > p->x) || (x < q->x)) { + return DBL_MAX; /* X-coordinate outside line segment. */ + } + } else { + if ((x > q->x) || (x < p->x)) { + return DBL_MAX; /* X-coordinate outside line segment. */ + } + } + dx = p->x - q->x; + dy = p->y - q->y; + t->x = (double)x; + if (FABS(dx) < DBL_EPSILON) { + double d1, d2; + /* + * Same X-coordinate indicates a vertical line. Pick the closest end + * point. + */ + d1 = p->y - y; + d2 = q->y - y; + if (FABS(d1) < FABS(d2)) { + t->y = p->y, d = d1; + } else { + t->y = q->y, d = d2; + } + } else if (FABS(dy) < DBL_EPSILON) { + /* Horizontal line. */ + t->y = p->y, d = p->y - y; + } else { + double m, b; + + m = dy / dx; + b = p->y - (m * p->x); + t->y = (x * m) + b; + d = y - t->y; + } + return FABS(d); +} + +static double +DistanceToYProc( + int x, int y, /* Search X-Y coordinate. */ + Point2d *p, Point2d *q, /* End points of the line segment. */ + Point2d *t) /* (out) Point on line segment. */ +{ + double dx, dy; + double d; + + if (p->y > q->y) { + if ((y > p->y) || (y < q->y)) { + return DBL_MAX; + } + } else { + if ((y > q->y) || (y < p->y)) { + return DBL_MAX; + } + } + dx = p->x - q->x; + dy = p->y - q->y; + t->y = y; + if (FABS(dy) < DBL_EPSILON) { + double d1, d2; + + /* Save Y-coordinate indicates an horizontal line. Pick the closest end + * point. */ + d1 = p->x - x; + d2 = q->x - x; + if (FABS(d1) < FABS(d2)) { + t->x = p->x, d = d1; + } else { + t->x = q->x, d = d2; + } + } else if (FABS(dx) < DBL_EPSILON) { + /* Vertical line. */ + t->x = p->x, d = p->x - x; + } else { + double m, b; + + m = dy / dx; + b = p->y - (m * p->x); + t->x = (y - b) / m; + d = x - t->x; + } + return FABS(d); +} + +/* + *--------------------------------------------------------------------------- + * + * ClosestTrace -- + * + * Find the line segment closest to the given window coordinate in the + * element. + * + * Results: + * If a new minimum distance is found, the information regarding it is + * returned via searchPtr. + * + *--------------------------------------------------------------------------- + */ +static int +ClosestTrace( + Graph *graphPtr, /* Graph widget record */ + LineElement *elemPtr, + ClosestSearch *searchPtr, /* Info about closest point in + * element */ + DistanceProc *distProc) +{ + Blt_ChainLink link; + Point2d closest; + double dMin; + int iClose; + + iClose = -1; /* Suppress compiler warning. */ + dMin = searchPtr->dist; + closest.x = closest.y = 0; /* Suppress compiler warning. */ + for (link = Blt_Chain_FirstLink(elemPtr->traces); link != NULL; + link = Blt_Chain_NextLink(link)) { + Trace *tracePtr; + Point2d *p, *pend; + + tracePtr = Blt_Chain_GetValue(link); + for (p = tracePtr->screenPts.points, + pend = p + (tracePtr->screenPts.length - 1); p < pend; p++) { + Point2d b; + double d; + + d = (*distProc)(searchPtr->x, searchPtr->y, p, p + 1, &b); + if (d < dMin) { + closest = b; + iClose = tracePtr->screenPts.map[p-tracePtr->screenPts.points]; + dMin = d; + } + } + } + if (dMin < searchPtr->dist) { + searchPtr->dist = dMin; + searchPtr->elemPtr = (Element *)elemPtr; + searchPtr->index = iClose; + searchPtr->point = Blt_InvMap2D(graphPtr, closest.x, closest.y, + &elemPtr->axes); + return TRUE; + } + return FALSE; +} + +/* + *--------------------------------------------------------------------------- + * + * ClosestStrip -- + * + * Find the line segment closest to the given window coordinate in the + * element. + * + * Results: + * If a new minimum distance is found, the information regarding it is + * returned via searchPtr. + * + *--------------------------------------------------------------------------- + */ +static int +ClosestStrip( + Graph *graphPtr, /* Graph widget record */ + LineElement *elemPtr, /* Line element record */ + ClosestSearch *searchPtr, /* Info about closest point in + * element */ + DistanceProc *distProc) +{ + Point2d closest; + double dMin; + int count; + int iClose; + Segment2d *sp; + + iClose = 0; + dMin = searchPtr->dist; + closest.x = closest.y = 0; + for (sp = elemPtr->lines.segments, count = 0; + count < elemPtr->lines.length; count++, sp++) { + double d; + Point2d b; + + d = (*distProc)(searchPtr->x, searchPtr->y, &sp->p, &sp->q, &b); + if (d < dMin) { + closest = b; + iClose = elemPtr->lines.map[count]; + dMin = d; + } + } + if (dMin < searchPtr->dist) { + searchPtr->dist = dMin; + searchPtr->elemPtr = (Element *)elemPtr; + searchPtr->index = iClose; + searchPtr->point = Blt_InvMap2D(graphPtr, closest.x, closest.y, + &elemPtr->axes); + return TRUE; + } + return FALSE; +} + +/* + *--------------------------------------------------------------------------- + * + * ClosestPoint -- + * + * Find the element whose data point is closest to the given screen + * coordinate. + * + * Results: + * If a new minimum distance is found, the information regarding + * it is returned via searchPtr. + * + *--------------------------------------------------------------------------- + */ +static void +ClosestPoint( + LineElement *elemPtr, /* Line element to be searched. */ + ClosestSearch *searchPtr) /* Assorted information related to + * searching for the closest point */ +{ + double dMin; + int count, iClose; + Point2d *pp; + + dMin = searchPtr->dist; + iClose = 0; + + /* + * Instead of testing each data point in graph coordinates, look at the + * array of mapped screen coordinates. The advantages are + * 1) only examine points that are visible (unclipped), and + * 2) the computed distance is already in screen coordinates. + */ + for (pp = elemPtr->symbolPts.points, count = 0; + count < elemPtr->symbolPts.length; count++, pp++) { + double dx, dy; + double d; + + dx = (double)(searchPtr->x - pp->x); + dy = (double)(searchPtr->y - pp->y); + if (searchPtr->along == SEARCH_BOTH) { + d = hypot(dx, dy); + } else if (searchPtr->along == SEARCH_X) { + d = dx; + } else if (searchPtr->along == SEARCH_Y) { + d = dy; + } else { + /* This can't happen */ + continue; + } + if (d < dMin) { + iClose = elemPtr->symbolPts.map[count]; + dMin = d; + } + } + if (dMin < searchPtr->dist) { + searchPtr->elemPtr = (Element *)elemPtr; + searchPtr->dist = dMin; + searchPtr->index = iClose; + searchPtr->point.x = elemPtr->x.values[iClose]; + searchPtr->point.y = elemPtr->y.values[iClose]; + } +} + +/* + *--------------------------------------------------------------------------- + * + * GetLineExtentsProc -- + * + * Retrieves the range of the line element + * + * Results: + * Returns the number of data points in the element. + * + *--------------------------------------------------------------------------- + */ +static void +GetLineExtentsProc(Element *basePtr, Region2d *extsPtr) +{ + LineElement *elemPtr = (LineElement *)basePtr; + int np; + + extsPtr->top = extsPtr->left = DBL_MAX; + extsPtr->bottom = extsPtr->right = -DBL_MAX; + + np = NUMBEROFPOINTS(elemPtr); + if (np < 1) { + return; + } + extsPtr->right = elemPtr->x.max; + if ((elemPtr->x.min <= 0.0) && (elemPtr->axes.x->logScale)) { + extsPtr->left = Blt_FindElemValuesMinimum(&elemPtr->x, DBL_MIN); + } else { + extsPtr->left = elemPtr->x.min; + } + extsPtr->bottom = elemPtr->y.max; + if ((elemPtr->y.min <= 0.0) && (elemPtr->axes.y->logScale)) { + extsPtr->top = Blt_FindElemValuesMinimum(&elemPtr->y, DBL_MIN); + } else { + extsPtr->top = elemPtr->y.min; + } + + /* Correct the data limits for error bars */ + + if (elemPtr->xError.nValues > 0) { + int i; + + np = MIN(elemPtr->xError.nValues, np); + for (i = 0; i < np; i++) { + double x; + + x = elemPtr->x.values[i] + elemPtr->xError.values[i]; + if (x > extsPtr->right) { + extsPtr->right = x; + } + x = elemPtr->x.values[i] - elemPtr->xError.values[i]; + if (elemPtr->axes.x->logScale) { + if (x < 0.0) { + x = -x; /* Mirror negative values, instead of + * ignoring them. */ + } + if ((x > DBL_MIN) && (x < extsPtr->left)) { + extsPtr->left = x; + } + } else if (x < extsPtr->left) { + extsPtr->left = x; + } + } + } else { + if ((elemPtr->xHigh.nValues > 0) && + (elemPtr->xHigh.max > extsPtr->right)) { + extsPtr->right = elemPtr->xHigh.max; + } + if (elemPtr->xLow.nValues > 0) { + double left; + + if ((elemPtr->xLow.min <= 0.0) && + (elemPtr->axes.x->logScale)) { + left = Blt_FindElemValuesMinimum(&elemPtr->xLow, DBL_MIN); + } else { + left = elemPtr->xLow.min; + } + if (left < extsPtr->left) { + extsPtr->left = left; + } + } + } + + if (elemPtr->yError.nValues > 0) { + int i; + + np = MIN(elemPtr->yError.nValues, np); + for (i = 0; i < np; i++) { + double y; + + y = elemPtr->y.values[i] + elemPtr->yError.values[i]; + if (y > extsPtr->bottom) { + extsPtr->bottom = y; + } + y = elemPtr->y.values[i] - elemPtr->yError.values[i]; + if (elemPtr->axes.y->logScale) { + if (y < 0.0) { + y = -y; /* Mirror negative values, instead of + * ignoring them. */ + } + if ((y > DBL_MIN) && (y < extsPtr->left)) { + extsPtr->top = y; + } + } else if (y < extsPtr->top) { + extsPtr->top = y; + } + } + } else { + if ((elemPtr->yHigh.nValues > 0) && + (elemPtr->yHigh.max > extsPtr->bottom)) { + extsPtr->bottom = elemPtr->yHigh.max; + } + if (elemPtr->yLow.nValues > 0) { + double top; + + if ((elemPtr->yLow.min <= 0.0) && + (elemPtr->axes.y->logScale)) { + top = Blt_FindElemValuesMinimum(&elemPtr->yLow, DBL_MIN); + } else { + top = elemPtr->yLow.min; + } + if (top < extsPtr->top) { + extsPtr->top = top; + } + } + } +} + +/* + *--------------------------------------------------------------------------- + * + * BackgroundChangedProc + * + * Results: + * None. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static void +BackgroundChangedProc(ClientData clientData) +{ + Element *elemPtr = clientData; + Graph *graphPtr; + + graphPtr = elemPtr->obj.graphPtr; + if (graphPtr->tkwin != NULL) { + graphPtr->flags |= REDRAW_WORLD; + Blt_EventuallyRedrawGraph(graphPtr); + } +} + +/* + *--------------------------------------------------------------------------- + * + * ConfigureLineProc -- + * + * Sets up the appropriate configuration parameters in the GC. It is + * assumed the parameters have been previously set by a call to + * Blt_ConfigureWidget. + * + * Results: + * The return value is a standard TCL result. If TCL_ERROR is returned, + * then interp->result contains an error message. + * + * Side effects: + * Configuration information such as line width, line style, color + * etc. get set in a new GC. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +ConfigureLineProc(Graph *graphPtr, Element *basePtr) +{ + LineElement *elemPtr = (LineElement *)basePtr; + unsigned long gcMask; + XGCValues gcValues; + GC newGC; + Blt_ChainLink link; + LineStyle *stylePtr; + + if (ConfigurePenProc(graphPtr, (Pen *)&elemPtr->builtinPen) != TCL_OK) { + return TCL_ERROR; + } + /* + * Point to the static normal/active pens if no external pens have been + * selected. + */ + link = Blt_Chain_FirstLink(elemPtr->styles); + if (link == NULL) { + link = Blt_Chain_AllocLink(sizeof(LineStyle)); + Blt_Chain_LinkAfter(elemPtr->styles, link, NULL); + } + stylePtr = Blt_Chain_GetValue(link); + stylePtr->penPtr = NORMALPEN(elemPtr); + + if (elemPtr->fillBg != NULL) { + Blt_SetBackgroundChangedProc(elemPtr->fillBg, BackgroundChangedProc, + elemPtr); + } + /* + * Set the outline GC for this pen: GCForeground is outline color. + * GCBackground is the fill color (only used for bitmap symbols). + */ + gcMask = 0; + if (elemPtr->fillFgColor != NULL) { + gcMask |= GCForeground; + gcValues.foreground = elemPtr->fillFgColor->pixel; + } + if (elemPtr->fillBgColor != NULL) { + gcMask |= GCBackground; + gcValues.background = elemPtr->fillBgColor->pixel; + } + newGC = Tk_GetGC(graphPtr->tkwin, gcMask, &gcValues); + if (elemPtr->fillGC != NULL) { + Tk_FreeGC(graphPtr->display, elemPtr->fillGC); + } + elemPtr->fillGC = newGC; + + if (Blt_ConfigModified(elemPtr->configSpecs, "-scalesymbols", + (char *)NULL)) { + elemPtr->flags |= (MAP_ITEM | SCALE_SYMBOL); + } + if (Blt_ConfigModified(elemPtr->configSpecs, "-pixels", "-trace", + "-*data", "-smooth", "-map*", "-label", "-hide", "-x", "-y", + "-areabackground", (char *)NULL)) { + elemPtr->flags |= MAP_ITEM; + } + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * ClosestLineProc -- + * + * Find the closest point or line segment (if interpolated) to the given + * window coordinate in the line element. + * + * Results: + * Returns the distance of the closest point among other information. + * + *--------------------------------------------------------------------------- + */ +static void +ClosestLineProc(Graph *graphPtr, Element *basePtr, ClosestSearch *searchPtr) +{ + LineElement *elemPtr = (LineElement *)basePtr; + int mode; + + mode = searchPtr->mode; + if (mode == SEARCH_AUTO) { + LinePen *penPtr; + + penPtr = NORMALPEN(elemPtr); + mode = SEARCH_POINTS; + if ((NUMBEROFPOINTS(elemPtr) > 1) && (penPtr->traceWidth > 0)) { + mode = SEARCH_TRACES; + } + } + if (mode == SEARCH_POINTS) { + ClosestPoint(elemPtr, searchPtr); + } else { + DistanceProc *distProc; + int found; + + if (searchPtr->along == SEARCH_X) { + distProc = DistanceToXProc; + } else if (searchPtr->along == SEARCH_Y) { + distProc = DistanceToYProc; + } else { + distProc = DistanceToLineProc; + } + if (elemPtr->obj.classId == CID_ELEM_STRIP) { + found = ClosestStrip(graphPtr, elemPtr, searchPtr, distProc); + } else { + found = ClosestTrace(graphPtr, elemPtr, searchPtr, distProc); + } + if ((!found) && (searchPtr->along != SEARCH_BOTH)) { + ClosestPoint(elemPtr, searchPtr); + } + } +} + +/* + * XDrawLines() points: XMaxRequestSize(dpy) - 3 + * XFillPolygon() points: XMaxRequestSize(dpy) - 4 + * XDrawSegments() segments: (XMaxRequestSize(dpy) - 3) / 2 + * XDrawRectangles() rectangles: (XMaxRequestSize(dpy) - 3) / 2 + * XFillRectangles() rectangles: (XMaxRequestSize(dpy) - 3) / 2 + * XDrawArcs() or XFillArcs() arcs: (XMaxRequestSize(dpy) - 3) / 3 + */ + +#define MAX_DRAWLINES(d) Blt_MaxRequestSize(d, sizeof(XPoint)) +#define MAX_DRAWPOLYGON(d) Blt_MaxRequestSize(d, sizeof(XPoint)) +#define MAX_DRAWSEGMENTS(d) Blt_MaxRequestSize(d, sizeof(XSegment)) +#define MAX_DRAWRECTANGLES(d) Blt_MaxRequestSize(d, sizeof(XRectangle)) +#define MAX_DRAWARCS(d) Blt_MaxRequestSize(d, sizeof(XArc)) + +#ifdef WIN32 + +static void +DrawCircles( + Display *display, + Drawable drawable, + LineElement *elemPtr, + LinePen *penPtr, + int nSymbolPts, + Point2d *symbolPts, + int radius) +{ + HBRUSH brush, oldBrush; + HPEN pen, oldPen; + HDC dc; + TkWinDCState state; + + if (drawable == None) { + return; /* Huh? */ + } + if ((penPtr->symbol.fillGC == NULL) && + (penPtr->symbol.outlineWidth == 0)) { + return; + } + dc = TkWinGetDrawableDC(display, drawable, &state); + /* SetROP2(dc, tkpWinRopModes[penPtr->symbol.fillGC->function]); */ + if (penPtr->symbol.fillGC != NULL) { + brush = CreateSolidBrush(penPtr->symbol.fillGC->foreground); + } else { + brush = GetStockBrush(NULL_BRUSH); + } + if (penPtr->symbol.outlineWidth > 0) { + pen = Blt_GCToPen(dc, penPtr->symbol.outlineGC); + } else { + pen = GetStockPen(NULL_PEN); + } + oldPen = SelectPen(dc, pen); + oldBrush = SelectBrush(dc, brush); + { + Point2d *pp, *pend; + + for (pp = symbolPts, pend = pp + nSymbolPts; pp < pend; pp++) { + int rndx, rndy; + rndx = Round(pp->x), rndy = Round(pp->y); + Ellipse(dc, rndx - radius, rndy - radius, rndx + radius + 1, + rndy + radius + 1); + } + } + DeleteBrush(SelectBrush(dc, oldBrush)); + DeletePen(SelectPen(dc, oldPen)); + TkWinReleaseDrawableDC(drawable, dc, &state); +} + +#else + +static void +DrawCircles(Display *display, Drawable drawable, LineElement *elemPtr, + LinePen *penPtr, int nSymbolPts, Point2d *symbolPts, int radius) +{ + int i; + XArc *arcs; /* Array of arcs (circle) */ + int reqSize; + int s; + int count; + + s = radius + radius; + arcs = Blt_AssertMalloc(nSymbolPts * sizeof(XArc)); + + if (elemPtr->symbolInterval > 0) { + Point2d *pp, *pend; + XArc *ap; + + ap = arcs; + count = 0; + for (pp = symbolPts, pend = pp + nSymbolPts; pp < pend; pp++) { + if (DRAW_SYMBOL(elemPtr)) { + ap->x = Round(pp->x) - radius; + ap->y = Round(pp->y) - radius; + ap->width = ap->height = (unsigned short)s; + ap->angle1 = 0; + ap->angle2 = 23040; + ap++, count++; + } + elemPtr->symbolCounter++; + } + } else { + Point2d *pp, *pend; + XArc *ap; + + ap = arcs; + for (pp = symbolPts, pend = pp + nSymbolPts; pp < pend; pp++) { + ap->x = Round(pp->x) - radius; + ap->y = Round(pp->y) - radius; + ap->width = ap->height = (unsigned short)s; + ap->angle1 = 0; + ap->angle2 = 23040; + ap++; + } + count = nSymbolPts; + } + reqSize = MAX_DRAWARCS(display); + for (i = 0; i < count; i += reqSize) { + int n; + + n = ((i + reqSize) > count) ? (count - i) : reqSize; + if (penPtr->symbol.fillGC != NULL) { + XFillArcs(display, drawable, penPtr->symbol.fillGC, arcs + i, n); + } + if (penPtr->symbol.outlineWidth > 0) { + XDrawArcs(display, drawable, penPtr->symbol.outlineGC, arcs + i, n); + } + } + Blt_Free(arcs); +} + +#endif + +static void +DrawSquares(Display *display, Drawable drawable, LineElement *elemPtr, + LinePen *penPtr, int nSymbolPts, Point2d *symbolPts, int r) +{ + XRectangle *rectangles; + XRectangle *rp, *rend; + int reqSize; + int s, count; + + s = r + r; + rectangles = Blt_AssertMalloc(nSymbolPts * sizeof(XRectangle)); + if (elemPtr->symbolInterval > 0) { + Point2d *pp, *pend; + XRectangle *rp; + + count = 0; + rp = rectangles; + for (pp = symbolPts, pend = pp + nSymbolPts; pp < pend; pp++) { + if (DRAW_SYMBOL(elemPtr)) { + rp->x = Round(pp->x) - r; + rp->y = Round(pp->y) - r; + rp->width = rp->height = (unsigned short)s; + rp++, count++; + } + elemPtr->symbolCounter++; + } + } else { + Point2d *pp, *pend; + XRectangle *rp; + + rp = rectangles; + for (pp = symbolPts, pend = pp + nSymbolPts; pp < pend; pp++) { + rp->x = Round(pp->x) - r; + rp->y = Round(pp->y) - r; + rp->width = rp->height = (unsigned short)s; + rp++; + } + count = nSymbolPts; + } + reqSize = MAX_DRAWRECTANGLES(display) - 3; + for (rp = rectangles, rend = rp + count; rp < rend; rp += reqSize) { + int n; + + n = rend - rp; + if (n > reqSize) { + n = reqSize; + } + if (penPtr->symbol.fillGC != NULL) { + XFillRectangles(display, drawable, penPtr->symbol.fillGC, rp, n); + } + if (penPtr->symbol.outlineWidth > 0) { + XDrawRectangles(display, drawable, penPtr->symbol.outlineGC, rp, n); + } + } + Blt_Free(rectangles); +} + +/* + *--------------------------------------------------------------------------- + * + * DrawSymbols -- + * + * Draw the symbols centered at the each given x,y coordinate in the array + * of points. + * + * Results: + * None. + * + * Side Effects: + * Draws a symbol at each coordinate given. If active, only those + * coordinates which are currently active are drawn. + * + *--------------------------------------------------------------------------- + */ +static void +DrawSymbols( + Graph *graphPtr, /* Graph widget record */ + Drawable drawable, /* Pixmap or window to draw into */ + LineElement *elemPtr, + LinePen *penPtr, + int size, /* Size of element */ + int nSymbolPts, /* Number of coordinates in array */ + Point2d *symbolPts) /* Array of x,y coordinates for line */ +{ + XPoint pattern[13]; /* Template for polygon symbols */ + int r1, r2; + int count; +#define SQRT_PI 1.77245385090552 +#define S_RATIO 0.886226925452758 + + if (size < 3) { + if (penPtr->symbol.fillGC != NULL) { + Point2d *pp, *endp; + XPoint *points, *xpp; + + xpp = points = Blt_AssertMalloc(nSymbolPts * sizeof(XPoint)); + for (pp = symbolPts, endp = pp + nSymbolPts; pp < endp; pp++) { + xpp->x = Round(pp->x); + xpp->y = Round(pp->y); + xpp++; + } + XDrawPoints(graphPtr->display, drawable, penPtr->symbol.fillGC, + points, nSymbolPts, CoordModeOrigin); + Blt_Free(points); + } + return; + } + r1 = (int)ceil(size * 0.5); + r2 = (int)ceil(size * S_RATIO * 0.5); + + switch (penPtr->symbol.type) { + case SYMBOL_NONE: + break; + + case SYMBOL_SQUARE: + DrawSquares(graphPtr->display, drawable, elemPtr, penPtr, nSymbolPts, + symbolPts, r2); + break; + + case SYMBOL_CIRCLE: + DrawCircles(graphPtr->display, drawable, elemPtr, penPtr, nSymbolPts, + symbolPts, r1); + break; + + case SYMBOL_SPLUS: + case SYMBOL_SCROSS: + { + XSegment *segments; /* Array of line segments (splus, + * scross) */ + int i; + int reqSize, nSegs; + + if (penPtr->symbol.type == SYMBOL_SCROSS) { + r2 = Round((double)r2 * M_SQRT1_2); + pattern[3].y = pattern[2].x = pattern[0].x = pattern[0].y = -r2; + pattern[3].x = pattern[2].y = pattern[1].y = pattern[1].x = r2; + } else { + pattern[0].y = pattern[1].y = pattern[2].x = pattern[3].x = 0; + pattern[0].x = pattern[2].y = -r2; + pattern[1].x = pattern[3].y = r2; + } + segments = Blt_AssertMalloc(nSymbolPts * 2 * sizeof(XSegment)); + if (elemPtr->symbolInterval > 0) { + Point2d *pp, *endp; + XSegment *sp; + + sp = segments; + count = 0; + for (pp = symbolPts, endp = pp + nSymbolPts; pp < endp; pp++) { + if (DRAW_SYMBOL(elemPtr)) { + int rndx, rndy; + rndx = Round(pp->x), rndy = Round(pp->y); + sp->x1 = pattern[0].x + rndx; + sp->y1 = pattern[0].y + rndy; + sp->x2 = pattern[1].x + rndx; + sp->y2 = pattern[1].y + rndy; + sp++; + sp->x1 = pattern[2].x + rndx; + sp->y1 = pattern[2].y + rndy; + sp->x2 = pattern[3].x + rndx; + sp->y2 = pattern[3].y + rndy; + sp++; + count++; + } + elemPtr->symbolCounter++; + } + } else { + Point2d *pp, *endp; + XSegment *sp; + + sp = segments; + count = nSymbolPts; + for (pp = symbolPts, endp = pp + nSymbolPts; pp < endp; pp++) { + int rndx, rndy; + rndx = Round(pp->x), rndy = Round(pp->y); + sp->x1 = pattern[0].x + rndx; + sp->y1 = pattern[0].y + rndy; + sp->x2 = pattern[1].x + rndx; + sp->y2 = pattern[1].y + rndy; + sp++; + sp->x1 = pattern[2].x + rndx; + sp->y1 = pattern[2].y + rndy; + sp->x2 = pattern[3].x + rndx; + sp->y2 = pattern[3].y + rndy; + sp++; + } + } + nSegs = count * 2; + /* Always draw skinny symbols regardless of the outline width */ + reqSize = MAX_DRAWSEGMENTS(graphPtr->display); + for (i = 0; i < nSegs; i += reqSize) { + int chunk; + + chunk = ((i + reqSize) > nSegs) ? (nSegs - i) : reqSize; + XDrawSegments(graphPtr->display, drawable, + penPtr->symbol.outlineGC, segments + i, chunk); + } + Blt_Free(segments); + } + break; + + case SYMBOL_PLUS: + case SYMBOL_CROSS: + { + XPoint *polygon; + int d; /* Small delta for cross/plus + * thickness */ + + d = (r2 / 3); + + /* + * + * 2 3 The plus/cross symbol is a closed polygon + * of 12 points. The diagram to the left + * 0,12 1 4 5 represents the positions of the points + * x,y which are computed below. The extra + * 11 10 7 6 (thirteenth) point connects the first and + * last points. + * 9 8 + */ + + pattern[0].x = pattern[11].x = pattern[12].x = -r2; + pattern[2].x = pattern[1].x = pattern[10].x = pattern[9].x = -d; + pattern[3].x = pattern[4].x = pattern[7].x = pattern[8].x = d; + pattern[5].x = pattern[6].x = r2; + pattern[2].y = pattern[3].y = -r2; + pattern[0].y = pattern[1].y = pattern[4].y = pattern[5].y = + pattern[12].y = -d; + pattern[11].y = pattern[10].y = pattern[7].y = pattern[6].y = d; + pattern[9].y = pattern[8].y = r2; + + if (penPtr->symbol.type == SYMBOL_CROSS) { + int i; + + /* For the cross symbol, rotate the points by 45 degrees. */ + for (i = 0; i < 12; i++) { + double dx, dy; + + dx = (double)pattern[i].x * M_SQRT1_2; + dy = (double)pattern[i].y * M_SQRT1_2; + pattern[i].x = Round(dx - dy); + pattern[i].y = Round(dx + dy); + } + pattern[12] = pattern[0]; + } + polygon = Blt_AssertMalloc(nSymbolPts * 13 * sizeof(XPoint)); + if (elemPtr->symbolInterval > 0) { + Point2d *pp, *endp; + XPoint *xpp; + + count = 0; + xpp = polygon; + for (pp = symbolPts, endp = pp + nSymbolPts; pp < endp; pp++) { + if (DRAW_SYMBOL(elemPtr)) { + int i; + int rndx, rndy; + + rndx = Round(pp->x), rndy = Round(pp->y); + for (i = 0; i < 13; i++) { + xpp->x = pattern[i].x + rndx; + xpp->y = pattern[i].y + rndy; + xpp++; + } + count++; + } + elemPtr->symbolCounter++; + } + } else { + Point2d *pp, *endp; + XPoint *xpp; + + xpp = polygon; + for (pp = symbolPts, endp = pp + nSymbolPts; pp < endp; pp++) { + int i; + int rndx, rndy; + + rndx = Round(pp->x), rndy = Round(pp->y); + for (i = 0; i < 13; i++) { + xpp->x = pattern[i].x + rndx; + xpp->y = pattern[i].y + rndy; + xpp++; + } + } + count = nSymbolPts; + } + if (penPtr->symbol.fillGC != NULL) { + int i; + XPoint *xpp; + + for (xpp = polygon, i = 0; i < count; i++, xpp += 13) { + XFillPolygon(graphPtr->display, drawable, + penPtr->symbol.fillGC, xpp, 13, Complex, + CoordModeOrigin); + } + } + if (penPtr->symbol.outlineWidth > 0) { + int i; + XPoint *xpp; + + for (xpp = polygon, i = 0; i < count; i++, xpp += 13) { + XDrawLines(graphPtr->display, drawable, + penPtr->symbol.outlineGC, xpp, 13, CoordModeOrigin); + } + } + Blt_Free(polygon); + } + break; + + case SYMBOL_DIAMOND: + { + XPoint *polygon; + + /* + * + * The plus symbol is a closed polygon + * 1 of 4 points. The diagram to the left + * represents the positions of the points + * 0,4 x,y 2 which are computed below. The extra + * (fifth) point connects the first and + * 3 last points. + * + */ + pattern[1].y = pattern[0].x = -r1; + pattern[2].y = pattern[3].x = pattern[0].y = pattern[1].x = 0; + pattern[3].y = pattern[2].x = r1; + pattern[4] = pattern[0]; + + polygon = Blt_AssertMalloc(nSymbolPts * 5 * sizeof(XPoint)); + if (elemPtr->symbolInterval > 0) { + Point2d *pp, *endp; + XPoint *xpp; + + xpp = polygon; + count = 0; + for (pp = symbolPts, endp = pp + nSymbolPts; pp < endp; pp++) { + int i; + + if (DRAW_SYMBOL(elemPtr)) { + int rndx, rndy; + + rndx = Round(pp->x), rndy = Round(pp->y); + for (i = 0; i < 5; i++) { + xpp->x = pattern[i].x + rndx; + xpp->y = pattern[i].y + rndy; + xpp++; + } + count++; + } + elemPtr->symbolCounter++; + } + } else { + Point2d *pp, *endp; + XPoint *xpp; + + xpp = polygon; + for (pp = symbolPts, endp = pp + nSymbolPts; pp < endp; pp++) { + int i; + int rndx, rndy; + + rndx = Round(pp->x), rndy = Round(pp->y); + for (i = 0; i < 5; i++) { + xpp->x = pattern[i].x + rndx; + xpp->y = pattern[i].y + rndy; + xpp++; + } + } + count = nSymbolPts; + } + if (penPtr->symbol.fillGC != NULL) { + XPoint *xpp; + int i; + + for (xpp = polygon, i = 0; i < count; i++, xpp += 5) { + XFillPolygon(graphPtr->display, drawable, + penPtr->symbol.fillGC, xpp, 5, Convex, CoordModeOrigin); + + } + } + if (penPtr->symbol.outlineWidth > 0) { + XPoint *xpp; + int i; + + for (xpp = polygon, i = 0; i < count; i++, xpp += 5) { + XDrawLines(graphPtr->display, drawable, + penPtr->symbol.outlineGC, xpp, 5, CoordModeOrigin); + } + } + Blt_Free(polygon); + } + break; + + case SYMBOL_TRIANGLE: + case SYMBOL_ARROW: + { + XPoint *polygon; + double b; + int b2, h1, h2; +#define H_RATIO 1.1663402261671607 +#define B_RATIO 1.3467736870885982 +#define TAN30 0.57735026918962573 +#define COS30 0.86602540378443871 + + b = Round(size * B_RATIO * 0.7); + b2 = Round(b * 0.5); + h2 = Round(TAN30 * b2); + h1 = Round(b2 / COS30); + /* + * + * The triangle symbol is a closed polygon + * 0,3 of 3 points. The diagram to the left + * represents the positions of the points + * x,y which are computed below. The extra + * (fourth) point connects the first and + * 2 1 last points. + * + */ + + if (penPtr->symbol.type == SYMBOL_ARROW) { + pattern[3].x = pattern[0].x = 0; + pattern[3].y = pattern[0].y = h1; + pattern[1].x = b2; + pattern[2].y = pattern[1].y = -h2; + pattern[2].x = -b2; + } else { + pattern[3].x = pattern[0].x = 0; + pattern[3].y = pattern[0].y = -h1; + pattern[1].x = b2; + pattern[2].y = pattern[1].y = h2; + pattern[2].x = -b2; + } + polygon = Blt_AssertMalloc(nSymbolPts * 4 * sizeof(XPoint)); + if (elemPtr->symbolInterval > 0) { + Point2d *pp, *endp; + XPoint *xpp; + + xpp = polygon; + count = 0; + for (pp = symbolPts, endp = pp + nSymbolPts; pp < endp; pp++) { + int i; + + if (DRAW_SYMBOL(elemPtr)) { + int rndx, rndy; + + rndx = Round(pp->x), rndy = Round(pp->y); + for (i = 0; i < 4; i++) { + xpp->x = pattern[i].x + rndx; + xpp->y = pattern[i].y + rndy; + xpp++; + } + count++; + } + elemPtr->symbolCounter++; + } + } else { + Point2d *pp, *endp; + XPoint *xpp; + + xpp = polygon; + for (pp = symbolPts, endp = pp + nSymbolPts; pp < endp; pp++) { + int i; + int rndx, rndy; + + rndx = Round(pp->x), rndy = Round(pp->y); + for (i = 0; i < 4; i++) { + xpp->x = pattern[i].x + rndx; + xpp->y = pattern[i].y + rndy; + xpp++; + } + } + count = nSymbolPts; + } + if (penPtr->symbol.fillGC != NULL) { + XPoint *xpp; + int i; + + xpp = polygon; + for (xpp = polygon, i = 0; i < count; i++, xpp += 4) { + XFillPolygon(graphPtr->display, drawable, + penPtr->symbol.fillGC, xpp, 4, Convex, CoordModeOrigin); + } + } + if (penPtr->symbol.outlineWidth > 0) { + XPoint *xpp; + int i; + + xpp = polygon; + for (xpp = polygon, i = 0; i < count; i++, xpp += 4) { + XDrawLines(graphPtr->display, drawable, + penPtr->symbol.outlineGC, xpp, 4, CoordModeOrigin); + } + } + Blt_Free(polygon); + } + break; + + case SYMBOL_IMAGE: + { + int w, h; + int dx, dy; + + Tk_SizeOfImage(penPtr->symbol.image, &w, &h); + + dx = w / 2; + dy = h / 2; + if (elemPtr->symbolInterval > 0) { + Point2d *pp, *endp; + + for (pp = symbolPts, endp = pp + nSymbolPts; pp < endp; pp++) { + if (DRAW_SYMBOL(elemPtr)) { + int x, y; + + x = Round(pp->x) - dx; + y = Round(pp->y) - dy; + Tk_RedrawImage(penPtr->symbol.image, 0, 0, w, h, + drawable, x, y); + } + elemPtr->symbolCounter++; + } + } else { + Point2d *pp, *endp; + + for (pp = symbolPts, endp = pp + nSymbolPts; pp < endp; pp++) { + int x, y; + + x = Round(pp->x) - dx; + y = Round(pp->y) - dy; + Tk_RedrawImage(penPtr->symbol.image, 0, 0, w, h, + drawable, x, y); + } + } + } + break; + + case SYMBOL_BITMAP: + { + Pixmap bitmap, mask; + int w, h, bw, bh; + double scale, sx, sy; + int dx, dy; + + Tk_SizeOfBitmap(graphPtr->display, penPtr->symbol.bitmap, &w, &h); + mask = None; + + /* + * Compute the size of the scaled bitmap. Stretch the bitmap to fit + * a nxn bounding box. + */ + sx = (double)size / (double)w; + sy = (double)size / (double)h; + scale = MIN(sx, sy); + bw = (int)(w * scale); + bh = (int)(h * scale); + + XSetClipMask(graphPtr->display, penPtr->symbol.outlineGC, None); + if (penPtr->symbol.mask != None) { + mask = Blt_ScaleBitmap(graphPtr->tkwin, penPtr->symbol.mask, + w, h, bw, bh); + XSetClipMask(graphPtr->display, penPtr->symbol.outlineGC, mask); + } + bitmap = Blt_ScaleBitmap(graphPtr->tkwin, penPtr->symbol.bitmap, + w, h, bw, bh); + if (penPtr->symbol.fillGC == NULL) { + XSetClipMask(graphPtr->display, penPtr->symbol.outlineGC, + bitmap); + } + dx = bw / 2; + dy = bh / 2; + if (elemPtr->symbolInterval > 0) { + Point2d *pp, *endp; + + for (pp = symbolPts, endp = pp + nSymbolPts; pp < endp; pp++) { + if (DRAW_SYMBOL(elemPtr)) { + int x, y; + + x = Round(pp->x) - dx; + y = Round(pp->y) - dy; + if ((penPtr->symbol.fillGC == NULL) || (mask !=None)) { + XSetClipOrigin(graphPtr->display, + penPtr->symbol.outlineGC, x, y); + } + XCopyPlane(graphPtr->display, bitmap, drawable, + penPtr->symbol.outlineGC, 0, 0, bw, bh, x, y, 1); + } + elemPtr->symbolCounter++; + } + } else { + Point2d *pp, *endp; + + for (pp = symbolPts, endp = pp + nSymbolPts; pp < endp; pp++) { + int x, y; + + x = Round(pp->x) - dx; + y = Round(pp->y) - dy; + if ((penPtr->symbol.fillGC == NULL) || (mask != None)) { + XSetClipOrigin(graphPtr->display, + penPtr->symbol.outlineGC, x, y); + } + XCopyPlane(graphPtr->display, bitmap, drawable, + penPtr->symbol.outlineGC, 0, 0, bw, bh, x, y, 1); + } + } + Tk_FreePixmap(graphPtr->display, bitmap); + if (mask != None) { + Tk_FreePixmap(graphPtr->display, mask); + } + } + break; + } +} + +/* + *--------------------------------------------------------------------------- + * + * DrawSymbolProc -- + * + * Draw the symbol centered at the each given x,y coordinate. + * + * Results: + * None. + * + * Side Effects: + * Draws a symbol at the coordinate given. + * + *--------------------------------------------------------------------------- + */ +static void +DrawSymbolProc( + Graph *graphPtr, /* Graph widget record */ + Drawable drawable, /* Pixmap or window to draw into */ + Element *basePtr, /* Line element information */ + int x, int y, /* Center position of symbol */ + int size) /* Size of symbol. */ +{ + LineElement *elemPtr = (LineElement *)basePtr; + LinePen *penPtr; + + penPtr = NORMALPEN(elemPtr); + if (penPtr->traceWidth > 0) { + /* + * Draw an extra line offset by one pixel from the previous to give a + * thicker appearance. This is only for the legend entry. This routine + * is never called for drawing the actual line segments. + */ + XDrawLine(graphPtr->display, drawable, penPtr->traceGC, x - size, y, + x + size, y); + XDrawLine(graphPtr->display, drawable, penPtr->traceGC, x - size, y + 1, + x + size, y + 1); + } + if (penPtr->symbol.type != SYMBOL_NONE) { + Point2d point; + + point.x = x, point.y = y; + DrawSymbols(graphPtr, drawable, elemPtr, penPtr, size, 1, &point); + } +} + +#ifdef WIN32 + +static void +DrawTraces( + Graph *graphPtr, + Drawable drawable, /* Pixmap or window to draw into */ + LineElement *elemPtr, + LinePen *penPtr) +{ + Blt_ChainLink link; + HBRUSH brush, oldBrush; + HDC dc; + HPEN pen, oldPen; + POINT *points; + TkWinDCState state; + int np; + + /* + * Depending if the line is wide (> 1 pixel), arbitrarily break the line in + * sections of 100 points. This bit of weirdness has to do with wide + * geometric pens. The longer the polyline, the slower it draws. The trade + * off is that we lose dash and cap uniformity for unbearably slow polyline + * draws. + */ + if (penPtr->traceGC->line_width > 1) { + np = 100; + } else { + np = Blt_MaxRequestSize(graphPtr->display, sizeof(POINT)) - 1; + } + points = Blt_AssertMalloc((np + 1) * sizeof(POINT)); + + dc = TkWinGetDrawableDC(graphPtr->display, drawable, &state); + + pen = Blt_GCToPen(dc, penPtr->traceGC); + oldPen = SelectPen(dc, pen); + brush = CreateSolidBrush(penPtr->traceGC->foreground); + oldBrush = SelectBrush(dc, brush); + SetROP2(dc, tkpWinRopModes[penPtr->traceGC->function]); + + for (link = Blt_Chain_FirstLink(elemPtr->traces); link != NULL; + link = Blt_Chain_NextLink(link)) { + POINT *p; + Trace *tracePtr; + int count, remaining; + + tracePtr = Blt_Chain_GetValue(link); + + /* + * If the trace has to be split into separate XDrawLines calls, then the + * end point of the current trace is also the starting point of the new + * split. + */ + + /* Step 1. Convert and draw the first section of the trace. + * It may contain the entire trace. */ + + for (p = points, count = 0; + count < MIN(np, tracePtr->screenPts.length); + count++, p++) { + p->x = Round(tracePtr->screenPts.points[count].x); + p->y = Round(tracePtr->screenPts.points[count].y); + } + Polyline(dc, points, count); + + /* Step 2. Next handle any full-size chunks left. */ + + while ((count + np) < tracePtr->screenPts.length) { + int j; + + /* Start with the last point of the previous trace. */ + points[0].x = points[np - 1].x; + points[0].y = points[np - 1].y; + + for (p = points + 1, j = 0; j < np; j++, count++, p++) { + p->x = Round(tracePtr->screenPts.points[count].x); + p->y = Round(tracePtr->screenPts.points[count].y); + } + Polyline(dc, points, np + 1); + } + + /* Step 3. Convert and draw the remaining points. */ + + remaining = tracePtr->screenPts.length - count; + if (remaining > 0) { + /* Start with the last point of the previous trace. */ + points[0].x = points[np - 1].x; + points[0].y = points[np - 1].y; + + for (p = points + 1; count < tracePtr->screenPts.length; + count++, p++) { + p->x = Round(tracePtr->screenPts.points[count].x); + p->y = Round(tracePtr->screenPts.points[count].y); + } + Polyline(dc, points, remaining + 1); + } + } + Blt_Free(points); + DeletePen(SelectPen(dc, oldPen)); + DeleteBrush(SelectBrush(dc, oldBrush)); + TkWinReleaseDrawableDC(drawable, dc, &state); +} + +#else + +static void +DrawTraces(Graph *graphPtr, Drawable drawable, LineElement *elemPtr, + LinePen *penPtr) +{ + Blt_ChainLink link; + XPoint *points; + int np; + + np = Blt_MaxRequestSize(graphPtr->display, sizeof(XPoint)) - 1; + points = Blt_AssertMalloc((np + 1) * sizeof(XPoint)); + + for (link = Blt_Chain_FirstLink(elemPtr->traces); link != NULL; + link = Blt_Chain_NextLink(link)) { + XPoint *xpp; + Trace *tracePtr; + int remaining, count; + int n; + + tracePtr = Blt_Chain_GetValue(link); + + /* + * If the trace has to be split into separate XDrawLines calls, then the + * end point of the current trace is also the starting point of the new + * split. + */ + /* Step 1. Convert and draw the first section of the trace. + * It may contain the entire trace. */ + + n = MIN(np, tracePtr->screenPts.length); + for (xpp = points, count = 0; count < n; count++, xpp++) { + xpp->x = Round(tracePtr->screenPts.points[count].x); + xpp->y = Round(tracePtr->screenPts.points[count].y); + } + XDrawLines(graphPtr->display, drawable, penPtr->traceGC, points, + count, CoordModeOrigin); + + /* Step 2. Next handle any full-size chunks left. */ + + while ((count + np) < tracePtr->screenPts.length) { + int j; + + /* Start with the last point of the previous trace. */ + points[0].x = points[np - 1].x; + points[0].y = points[np - 1].y; + + for (xpp = points + 1, j = 0; j < np; j++, count++, xpp++) { + xpp->x = Round(tracePtr->screenPts.points[count].x); + xpp->y = Round(tracePtr->screenPts.points[count].y); + } + XDrawLines(graphPtr->display, drawable, penPtr->traceGC, points, + np + 1, CoordModeOrigin); + } + + /* Step 3. Convert and draw the remaining points. */ + + remaining = tracePtr->screenPts.length - count; + if (remaining > 0) { + /* Start with the last point of the previous trace. */ + points[0].x = points[np - 1].x; + points[0].y = points[np - 1].y; + for (xpp = points + 1; count < tracePtr->screenPts.length; count++, + xpp++) { + xpp->x = Round(tracePtr->screenPts.points[count].x); + xpp->y = Round(tracePtr->screenPts.points[count].y); + } + XDrawLines(graphPtr->display, drawable, penPtr->traceGC, points, + remaining + 1, CoordModeOrigin); + } + } + Blt_Free(points); +} +#endif /* WIN32 */ + +static void +DrawValues(Graph *graphPtr, Drawable drawable, LineElement *elemPtr, + LinePen *penPtr, int length, Point2d *points, int *map) +{ + Point2d *pp, *endp; + double *xval, *yval; + const char *fmt; + char string[TCL_DOUBLE_SPACE * 2 + 2]; + int count; + + fmt = penPtr->valueFormat; + if (fmt == NULL) { + fmt = "%g"; + } + count = 0; + xval = elemPtr->x.values, yval = elemPtr->y.values; + for (pp = points, endp = points + length; pp < endp; pp++) { + double x, y; + + x = xval[map[count]]; + y = yval[map[count]]; + count++; + if (penPtr->valueShow == SHOW_X) { + sprintf_s(string, TCL_DOUBLE_SPACE, fmt, x); + } else if (penPtr->valueShow == SHOW_Y) { + sprintf_s(string, TCL_DOUBLE_SPACE, fmt, y); + } else if (penPtr->valueShow == SHOW_BOTH) { + sprintf_s(string, TCL_DOUBLE_SPACE, fmt, x); + strcat(string, ","); + sprintf_s(string + strlen(string), TCL_DOUBLE_SPACE, fmt, y); + } + Blt_DrawText(graphPtr->tkwin, drawable, string, &penPtr->valueStyle, + Round(pp->x), Round(pp->y)); + } +} + +/* + *--------------------------------------------------------------------------- + * + * DrawActiveLineProc -- + * + * Draws the connected line(s) representing the element. If the line is + * made up of non-line symbols and the line width parameter has been set + * (linewidth > 0), the element will also be drawn as a line (with the + * linewidth requested). The line may consist of separate line segments. + * + * Results: + * None. + * + * Side effects: + * X drawing commands are output. + * + *--------------------------------------------------------------------------- + */ +static void +DrawActiveLineProc(Graph *graphPtr, Drawable drawable, Element *basePtr) +{ + LineElement *elemPtr = (LineElement *)basePtr; + LinePen *penPtr = (LinePen *)elemPtr->activePenPtr; + int symbolSize; + + if (penPtr == NULL) { + return; + } + symbolSize = ScaleSymbol(elemPtr, penPtr->symbol.size); + + /* + * nActiveIndices + * > 0 Some points are active. Uses activeArr. + * < 0 All points are active. + * == 0 No points are active. + */ + if (elemPtr->nActiveIndices > 0) { + if (elemPtr->flags & ACTIVE_PENDING) { + MapActiveSymbols(graphPtr, elemPtr); + } + if (penPtr->symbol.type != SYMBOL_NONE) { + DrawSymbols(graphPtr, drawable, elemPtr, penPtr, symbolSize, + elemPtr->activePts.length, elemPtr->activePts.points); + } + if (penPtr->valueShow != SHOW_NONE) { + DrawValues(graphPtr, drawable, elemPtr, penPtr, + elemPtr->activePts.length, + elemPtr->activePts.points, + elemPtr->activePts.map); + } + } else if (elemPtr->nActiveIndices < 0) { + if (penPtr->traceWidth > 0) { + if (elemPtr->lines.length > 0) { + Blt_Draw2DSegments(graphPtr->display, drawable, + penPtr->traceGC, elemPtr->lines.segments, + elemPtr->lines.length); + } else if (Blt_Chain_GetLength(elemPtr->traces) > 0) { + DrawTraces(graphPtr, drawable, elemPtr, penPtr); + } + } + if (penPtr->symbol.type != SYMBOL_NONE) { + DrawSymbols(graphPtr, drawable, elemPtr, penPtr, symbolSize, + elemPtr->symbolPts.length, elemPtr->symbolPts.points); + } + if (penPtr->valueShow != SHOW_NONE) { + DrawValues(graphPtr, drawable, elemPtr, penPtr, + elemPtr->symbolPts.length, elemPtr->symbolPts.points, + elemPtr->symbolPts.map); + } + } +} + +/* + *--------------------------------------------------------------------------- + * + * DrawNormalLine -- + * + * Draws the connected line(s) representing the element. If the line is + * made up of non-line symbols and the line width parameter has been set + * (linewidth > 0), the element will also be drawn as a line (with the + * linewidth requested). The line may consist of separate line segments. + * + * Results: + * None. + * + * Side effects: + * X drawing commands are output. + * + *--------------------------------------------------------------------------- + */ +static void +DrawNormalLineProc(Graph *graphPtr, Drawable drawable, Element *basePtr) +{ + LineElement *elemPtr = (LineElement *)basePtr; + Blt_ChainLink link; + unsigned int count; + + /* Fill area under the curve */ + if (elemPtr->fillPts != NULL) { + XPoint *points; + Point2d *endp, *pp; + + points = Blt_AssertMalloc(sizeof(XPoint) * elemPtr->nFillPts); + count = 0; + for (pp = elemPtr->fillPts, endp = pp + elemPtr->nFillPts; + pp < endp; pp++) { + points[count].x = Round(pp->x); + points[count].y = Round(pp->y); + count++; + } + if (elemPtr->fillBg != NULL) { + Blt_SetBackgroundOrigin(graphPtr->tkwin, elemPtr->fillBg, 0, 0); + Blt_FillBackgroundPolygon(graphPtr->tkwin, drawable, + elemPtr->fillBg, points, elemPtr->nFillPts, 0, TK_RELIEF_FLAT); + } + Blt_Free(points); + } + + /* Lines: stripchart segments or graph traces. */ + if (elemPtr->lines.length > 0) { + for (link = Blt_Chain_FirstLink(elemPtr->styles); + link != NULL; link = Blt_Chain_NextLink(link)) { + LineStyle *stylePtr; + LinePen *penPtr; + + stylePtr = Blt_Chain_GetValue(link); + penPtr = (LinePen *)stylePtr->penPtr; + if ((stylePtr->lines.length > 0) && + (penPtr->errorBarLineWidth > 0)) { + Blt_Draw2DSegments(graphPtr->display, drawable, penPtr->traceGC, + stylePtr->lines.segments, stylePtr->lines.length); + } + } + } else { + LinePen *penPtr; + + penPtr = NORMALPEN(elemPtr); + if ((Blt_Chain_GetLength(elemPtr->traces) > 0) && + (penPtr->traceWidth > 0)) { + DrawTraces(graphPtr, drawable, elemPtr, penPtr); + } + } + + if (elemPtr->reqMaxSymbols > 0) { + int total; + + total = 0; + for (link = Blt_Chain_FirstLink(elemPtr->styles); + link != NULL; link = Blt_Chain_NextLink(link)) { + LineStyle *stylePtr; + + stylePtr = Blt_Chain_GetValue(link); + total += stylePtr->symbolPts.length; + } + elemPtr->symbolInterval = total / elemPtr->reqMaxSymbols; + elemPtr->symbolCounter = 0; + } + + /* Symbols, error bars, values. */ + + count = 0; + for (link = Blt_Chain_FirstLink(elemPtr->styles); link != NULL; + link = Blt_Chain_NextLink(link)) { + LineStyle *stylePtr; + LinePen *penPtr; + + stylePtr = Blt_Chain_GetValue(link); + penPtr = (LinePen *)stylePtr->penPtr; + if ((stylePtr->xeb.length > 0) && (penPtr->errorBarShow & SHOW_X)) { + Blt_Draw2DSegments(graphPtr->display, drawable, penPtr->errorBarGC, + stylePtr->xeb.segments, stylePtr->xeb.length); + } + if ((stylePtr->yeb.length > 0) && (penPtr->errorBarShow & SHOW_Y)) { + Blt_Draw2DSegments(graphPtr->display, drawable, penPtr->errorBarGC, + stylePtr->yeb.segments, stylePtr->yeb.length); + } + if ((stylePtr->symbolPts.length > 0) && + (penPtr->symbol.type != SYMBOL_NONE)) { + DrawSymbols(graphPtr, drawable, elemPtr, penPtr, + stylePtr->symbolSize, stylePtr->symbolPts.length, + stylePtr->symbolPts.points); + } + if (penPtr->valueShow != SHOW_NONE) { + DrawValues(graphPtr, drawable, elemPtr, penPtr, + stylePtr->symbolPts.length, stylePtr->symbolPts.points, + elemPtr->symbolPts.map + count); + } + count += stylePtr->symbolPts.length; + } + elemPtr->symbolInterval = 0; +} + +/* + *--------------------------------------------------------------------------- + * + * GetSymbolPostScriptInfo -- + * + * Set up the PostScript environment with the macros and attributes needed + * to draw the symbols of the element. + * + * Results: + * None. + * + *--------------------------------------------------------------------------- + */ +static void +GetSymbolPostScriptInfo( + Graph *graphPtr, + Blt_Ps ps, + LinePen *penPtr, + int size) +{ + XColor *outlineColor, *fillColor, *defaultColor; + + /* Set line and foreground attributes */ + outlineColor = penPtr->symbol.outlineColor; + fillColor = penPtr->symbol.fillColor; + defaultColor = penPtr->traceColor; + + if (fillColor == COLOR_DEFAULT) { + fillColor = defaultColor; + } + if (outlineColor == COLOR_DEFAULT) { + outlineColor = defaultColor; + } + if (penPtr->symbol.type == SYMBOL_NONE) { + Blt_Ps_XSetLineAttributes(ps, defaultColor, penPtr->traceWidth + 2, + &penPtr->traceDashes, CapButt, JoinMiter); + } else { + Blt_Ps_XSetLineWidth(ps, penPtr->symbol.outlineWidth); + Blt_Ps_XSetDashes(ps, (Blt_Dashes *)NULL); + } + + /* + * Build a PostScript procedure to draw the symbols. For bitmaps, paint + * both the bitmap and its mask. Otherwise fill and stroke the path formed + * already. + */ + Blt_Ps_Append(ps, "\n/DrawSymbolProc {\n"); + switch (penPtr->symbol.type) { + case SYMBOL_NONE: + break; /* Do nothing */ + case SYMBOL_BITMAP: + { + int w, h; + double sx, sy, scale; + + /* + * Compute how much to scale the bitmap. Don't let the scaled + * bitmap exceed the bounding square for the symbol. + */ + Tk_SizeOfBitmap(graphPtr->display, penPtr->symbol.bitmap, &w, &h); + sx = (double)size / (double)w; + sy = (double)size / (double)h; + scale = MIN(sx, sy); + + if ((penPtr->symbol.mask != None) && (fillColor != NULL)) { + Blt_Ps_VarAppend(ps, "\n % Bitmap mask is \"", + Tk_NameOfBitmap(graphPtr->display, penPtr->symbol.mask), + "\"\n\n ", (char *)NULL); + Blt_Ps_XSetBackground(ps, fillColor); + Blt_Ps_DrawBitmap(ps, graphPtr->display, penPtr->symbol.mask, + scale, scale); + } + Blt_Ps_VarAppend(ps, "\n % Bitmap symbol is \"", + Tk_NameOfBitmap(graphPtr->display, penPtr->symbol.bitmap), + "\"\n\n ", (char *)NULL); + Blt_Ps_XSetForeground(ps, outlineColor); + Blt_Ps_DrawBitmap(ps, graphPtr->display, penPtr->symbol.bitmap, + scale, scale); + } + break; + default: + if (fillColor != NULL) { + Blt_Ps_Append(ps, " "); + Blt_Ps_XSetBackground(ps, fillColor); + Blt_Ps_Append(ps, " gsave fill grestore\n"); + } + if ((outlineColor != NULL) && (penPtr->symbol.outlineWidth > 0)) { + Blt_Ps_Append(ps, " "); + Blt_Ps_XSetForeground(ps, outlineColor); + Blt_Ps_Append(ps, " stroke\n"); + } + break; + } + Blt_Ps_Append(ps, "} def\n\n"); +} + +/* + *--------------------------------------------------------------------------- + * + * SymbolsToPostScript -- + * + * Draw a symbol centered at the given x,y window coordinate based upon the + * element symbol type and size. + * + * Results: + * None. + * + * Problems: + * Most notable is the round-off errors generated when calculating the + * centered position of the symbol. + * + *--------------------------------------------------------------------------- + */ +static void +SymbolsToPostScript( + Graph *graphPtr, + Blt_Ps ps, + LinePen *penPtr, + int size, + int nSymbolPts, + Point2d *symbolPts) +{ + double symbolSize; + static const char *symbolMacros[] = + { + "Li", "Sq", "Ci", "Di", "Pl", "Cr", "Sp", "Sc", "Tr", "Ar", "Bm", + (char *)NULL, + }; + GetSymbolPostScriptInfo(graphPtr, ps, penPtr, size); + + symbolSize = (double)size; + switch (penPtr->symbol.type) { + case SYMBOL_SQUARE: + case SYMBOL_CROSS: + case SYMBOL_PLUS: + case SYMBOL_SCROSS: + case SYMBOL_SPLUS: + symbolSize = (double)Round(size * S_RATIO); + break; + case SYMBOL_TRIANGLE: + case SYMBOL_ARROW: + symbolSize = (double)Round(size * 0.7); + break; + case SYMBOL_DIAMOND: + symbolSize = (double)Round(size * M_SQRT1_2); + break; + + default: + break; + } + { + Point2d *pp, *endp; + + for (pp = symbolPts, endp = symbolPts + nSymbolPts; pp < endp; pp++) { + Blt_Ps_Format(ps, "%g %g %g %s\n", pp->x, pp->y, + symbolSize, symbolMacros[penPtr->symbol.type]); + } + } +} + +/* + *--------------------------------------------------------------------------- + * + * SymbolToPostScriptProc -- + * + * Draw the symbol centered at the each given x,y coordinate. + * + * Results: + * None. + * + * Side Effects: + * Draws a symbol at the coordinate given. + * + *--------------------------------------------------------------------------- + */ +static void +SymbolToPostScriptProc( + Graph *graphPtr, /* Graph widget record */ + Blt_Ps ps, + Element *basePtr, /* Line element information */ + double x, double y, /* Center position of symbol */ + int size) /* Size of element */ +{ + LineElement *elemPtr = (LineElement *)basePtr; + LinePen *penPtr; + + penPtr = NORMALPEN(elemPtr); + if (penPtr->traceWidth > 0) { + /* + * Draw an extra line offset by one pixel from the previous to give a + * thicker appearance. This is only for the legend entry. This routine + * is never called for drawing the actual line segments. + */ + Blt_Ps_XSetLineAttributes(ps, penPtr->traceColor, + penPtr->traceWidth, &penPtr->traceDashes, CapButt, JoinMiter); + Blt_Ps_Format(ps, "%g %g %d Li\n", x, y, size + size); + } + if (penPtr->symbol.type != SYMBOL_NONE) { + Point2d point; + + point.x = x, point.y = y; + SymbolsToPostScript(graphPtr, ps, penPtr, size, 1, &point); + } +} + +static void +SetLineAttributes(Blt_Ps ps, LinePen *penPtr) +{ + /* Set the attributes of the line (color, dashes, linewidth) */ + Blt_Ps_XSetLineAttributes(ps, penPtr->traceColor, + penPtr->traceWidth, &penPtr->traceDashes, CapButt, JoinMiter); + if ((LineIsDashed(penPtr->traceDashes)) && + (penPtr->traceOffColor != NULL)) { + Blt_Ps_Append(ps, "/DashesProc {\n gsave\n "); + Blt_Ps_XSetBackground(ps, penPtr->traceOffColor); + Blt_Ps_Append(ps, " "); + Blt_Ps_XSetDashes(ps, (Blt_Dashes *)NULL); + Blt_Ps_Append(ps, "stroke\n grestore\n} def\n"); + } else { + Blt_Ps_Append(ps, "/DashesProc {} def\n"); + } +} + +static void +TracesToPostScript(Blt_Ps ps, LineElement *elemPtr, LinePen *penPtr) +{ + Blt_ChainLink link; + + SetLineAttributes(ps, penPtr); + for (link = Blt_Chain_FirstLink(elemPtr->traces); link != NULL; + link = Blt_Chain_NextLink(link)) { + Trace *tracePtr; + + tracePtr = Blt_Chain_GetValue(link); + if (tracePtr->screenPts.length > 0) { + Blt_Ps_Append(ps, "% start trace\n"); + Blt_Ps_DrawPolyline(ps, tracePtr->screenPts.points, + tracePtr->screenPts.length); + Blt_Ps_Append(ps, "% end trace\n"); + } + } +} + + +static void +ValuesToPostScript(Blt_Ps ps, LineElement *elemPtr, LinePen *penPtr, + int nSymbolPts, Point2d *symbolPts, int *pointToData) +{ + Point2d *pp, *endp; + int count; + char string[TCL_DOUBLE_SPACE * 2 + 2]; + const char *fmt; + + fmt = penPtr->valueFormat; + if (fmt == NULL) { + fmt = "%g"; + } + count = 0; + for (pp = symbolPts, endp = symbolPts + nSymbolPts; pp < endp; pp++) { + double x, y; + + x = elemPtr->x.values[pointToData[count]]; + y = elemPtr->y.values[pointToData[count]]; + count++; + if (penPtr->valueShow == SHOW_X) { + sprintf_s(string, TCL_DOUBLE_SPACE, fmt, x); + } else if (penPtr->valueShow == SHOW_Y) { + sprintf_s(string, TCL_DOUBLE_SPACE, fmt, y); + } else if (penPtr->valueShow == SHOW_BOTH) { + sprintf_s(string, TCL_DOUBLE_SPACE, fmt, x); + strcat(string, ","); + sprintf_s(string + strlen(string), TCL_DOUBLE_SPACE, fmt, y); + } + Blt_Ps_DrawText(ps, string, &penPtr->valueStyle, pp->x, pp->y); + } +} + + +/* + *--------------------------------------------------------------------------- + * + * ActiveLineToPostScript -- + * + * Generates PostScript commands to draw as "active" the points (symbols) + * and or line segments (trace) representing the element. + * + * Results: + * None. + * + * Side effects: + * PostScript pen width, dashes, and color settings are changed. + * + *--------------------------------------------------------------------------- + */ +static void +ActiveLineToPostScriptProc(Graph *graphPtr, Blt_Ps ps, Element *basePtr) +{ + LineElement *elemPtr = (LineElement *)basePtr; + LinePen *penPtr = (LinePen *)elemPtr->activePenPtr; + int symbolSize; + + if (penPtr == NULL) { + return; + } + symbolSize = ScaleSymbol(elemPtr, penPtr->symbol.size); + if (elemPtr->nActiveIndices > 0) { + if (elemPtr->flags & ACTIVE_PENDING) { + MapActiveSymbols(graphPtr, elemPtr); + } + if (penPtr->symbol.type != SYMBOL_NONE) { + SymbolsToPostScript(graphPtr, ps, penPtr, symbolSize, + elemPtr->activePts.length, elemPtr->activePts.points); + } + if (penPtr->valueShow != SHOW_NONE) { + ValuesToPostScript(ps, elemPtr, penPtr, elemPtr->activePts.length, + elemPtr->activePts.points, elemPtr->activePts.map); + } + } else if (elemPtr->nActiveIndices < 0) { + if (penPtr->traceWidth > 0) { + if (elemPtr->lines.length > 0) { + SetLineAttributes(ps, penPtr); + Blt_Ps_Draw2DSegments(ps, elemPtr->lines.segments, + elemPtr->lines.length); + } + if (Blt_Chain_GetLength(elemPtr->traces) > 0) { + TracesToPostScript(ps, elemPtr, (LinePen *)penPtr); + } + } + if (penPtr->symbol.type != SYMBOL_NONE) { + SymbolsToPostScript(graphPtr, ps, penPtr, symbolSize, + elemPtr->symbolPts.length, elemPtr->symbolPts.points); + } + if (penPtr->valueShow != SHOW_NONE) { + ValuesToPostScript(ps, elemPtr, penPtr, elemPtr->symbolPts.length, + elemPtr->symbolPts.points, elemPtr->symbolPts.map); + } + } +} + +/* + *--------------------------------------------------------------------------- + * + * NormalLineToPostScriptProc -- + * + * Similar to the DrawLine procedure, prints PostScript related commands to + * form the connected line(s) representing the element. + * + * Results: + * None. + * + * Side effects: + * PostScript pen width, dashes, and color settings are changed. + * + *--------------------------------------------------------------------------- + */ +static void +NormalLineToPostScriptProc(Graph *graphPtr, Blt_Ps ps, Element *basePtr) +{ + LineElement *elemPtr = (LineElement *)basePtr; + Blt_ChainLink link; + unsigned int count; + + /* Draw fill area */ + if (elemPtr->fillPts != NULL) { + /* Create a path to use for both the polygon and its outline. */ + Blt_Ps_Append(ps, "% start fill area\n"); + Blt_Ps_Polyline(ps, elemPtr->fillPts, elemPtr->nFillPts); + + /* If the background fill color was specified, draw the polygon in a + * solid fashion with that color. */ + if (elemPtr->fillBgColor != NULL) { + Blt_Ps_XSetBackground(ps, elemPtr->fillBgColor); + Blt_Ps_Append(ps, "gsave fill grestore\n"); + } + Blt_Ps_XSetForeground(ps, elemPtr->fillFgColor); + if (elemPtr->fillBg != NULL) { + Blt_Ps_Append(ps, "gsave fill grestore\n"); + /* TBA: Transparent tiling is the hard part. */ + } else { + Blt_Ps_Append(ps, "gsave fill grestore\n"); + } + Blt_Ps_Append(ps, "% end fill area\n"); + } + + /* Draw lines (strip chart) or traces (xy graph) */ + if (elemPtr->lines.length > 0) { + for (link = Blt_Chain_FirstLink(elemPtr->styles); link != NULL; + link = Blt_Chain_NextLink(link)) { + LineStyle *stylePtr; + LinePen *penPtr; + + stylePtr = Blt_Chain_GetValue(link); + penPtr = (LinePen *)stylePtr->penPtr; + if ((stylePtr->lines.length > 0) && (penPtr->traceWidth > 0)) { + SetLineAttributes(ps, penPtr); + Blt_Ps_Append(ps, "% start segments\n"); + Blt_Ps_Draw2DSegments(ps, stylePtr->lines.segments, + stylePtr->lines.length); + Blt_Ps_Append(ps, "% end segments\n"); + } + } + } else { + LinePen *penPtr; + + penPtr = NORMALPEN(elemPtr); + if ((Blt_Chain_GetLength(elemPtr->traces) > 0) && + (penPtr->traceWidth > 0)) { + TracesToPostScript(ps, elemPtr, penPtr); + } + } + + /* Draw symbols, error bars, values. */ + + count = 0; + for (link = Blt_Chain_FirstLink(elemPtr->styles); link != NULL; + link = Blt_Chain_NextLink(link)) { + LineStyle *stylePtr; + LinePen *penPtr; + XColor *colorPtr; + + stylePtr = Blt_Chain_GetValue(link); + penPtr = (LinePen *)stylePtr->penPtr; + colorPtr = penPtr->errorBarColor; + if (colorPtr == COLOR_DEFAULT) { + colorPtr = penPtr->traceColor; + } + if ((stylePtr->xeb.length > 0) && (penPtr->errorBarShow & SHOW_X)) { + Blt_Ps_XSetLineAttributes(ps, colorPtr, penPtr->errorBarLineWidth, + NULL, CapButt, JoinMiter); + Blt_Ps_Draw2DSegments(ps, stylePtr->xeb.segments, + stylePtr->xeb.length); + } + if ((stylePtr->yeb.length > 0) && (penPtr->errorBarShow & SHOW_Y)) { + Blt_Ps_XSetLineAttributes(ps, colorPtr, penPtr->errorBarLineWidth, + NULL, CapButt, JoinMiter); + Blt_Ps_Draw2DSegments(ps, stylePtr->yeb.segments, + stylePtr->yeb.length); + } + if ((stylePtr->symbolPts.length > 0) && + (penPtr->symbol.type != SYMBOL_NONE)) { + SymbolsToPostScript(graphPtr, ps, penPtr, stylePtr->symbolSize, + stylePtr->symbolPts.length, stylePtr->symbolPts.points); + } + if (penPtr->valueShow != SHOW_NONE) { + ValuesToPostScript(ps, elemPtr, penPtr, stylePtr->symbolPts.length, + stylePtr->symbolPts.points, elemPtr->symbolPts.map + count); + } + count += stylePtr->symbolPts.length; + } +} + +/* + *--------------------------------------------------------------------------- + * + * DestroyLineProc -- + * + * Release memory and resources allocated for the line element. + * + * Results: + * None. + * + * Side effects: + * Everything associated with the line element is freed up. + * + *--------------------------------------------------------------------------- + */ + +static void +DestroyLineProc(Graph *graphPtr, Element *basePtr) +{ + LineElement *elemPtr = (LineElement *)basePtr; + + DestroyPenProc(graphPtr, (Pen *)&elemPtr->builtinPen); + if (elemPtr->activePenPtr != NULL) { + Blt_FreePen((Pen *)elemPtr->activePenPtr); + } + ResetLine(elemPtr); + if (elemPtr->styles != NULL) { + Blt_FreeStylePalette(elemPtr->styles); + Blt_Chain_Destroy(elemPtr->styles); + } + if (elemPtr->activeIndices != NULL) { + Blt_Free(elemPtr->activeIndices); + } + if (elemPtr->fillPts != NULL) { + Blt_Free(elemPtr->fillPts); + } + if (elemPtr->fillGC != NULL) { + Tk_FreeGC(graphPtr->display, elemPtr->fillGC); + } +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_LineElement -- + * + * Allocate memory and initialize methods for the new line element. + * + * Results: + * The pointer to the newly allocated element structure is returned. + * + * Side effects: + * Memory is allocated for the line element structure. + * + *--------------------------------------------------------------------------- + */ + +static ElementProcs lineProcs = +{ + ClosestLineProc, /* Finds the closest element/data + * point */ + ConfigureLineProc, /* Configures the element. */ + DestroyLineProc, /* Destroys the element. */ + DrawActiveLineProc, /* Draws active element */ + DrawNormalLineProc, /* Draws normal element */ + DrawSymbolProc, /* Draws the element symbol. */ + GetLineExtentsProc, /* Find the extents of the element's + * data. */ + ActiveLineToPostScriptProc, /* Prints active element. */ + NormalLineToPostScriptProc, /* Prints normal element. */ + SymbolToPostScriptProc, /* Prints the line's symbol. */ + MapLineProc /* Compute element's screen + * coordinates. */ +}; + +Element * +Blt_LineElement(Graph *graphPtr, const char *name, ClassId classId) +{ + LineElement *elemPtr; + + elemPtr = Blt_AssertCalloc(1, sizeof(LineElement)); + elemPtr->procsPtr = &lineProcs; + if (classId == CID_ELEM_LINE) { + elemPtr->configSpecs = lineElemConfigSpecs; + } else { + elemPtr->configSpecs = stripElemConfigSpecs; + } + elemPtr->obj.name = Blt_AssertStrdup(name); + Blt_GraphSetObjectClass(&elemPtr->obj, classId); + elemPtr->flags = SCALE_SYMBOL; + elemPtr->obj.graphPtr = graphPtr; + /* By default an element's name and label are the same. */ + elemPtr->label = Blt_AssertStrdup(name); + elemPtr->legendRelief = TK_RELIEF_FLAT; + elemPtr->penDir = PEN_BOTH_DIRECTIONS; + elemPtr->styles = Blt_Chain_Create(); + elemPtr->builtinPenPtr = &elemPtr->builtinPen; + elemPtr->reqSmooth = PEN_SMOOTH_LINEAR; + InitLinePen(elemPtr->builtinPenPtr); + bltLineStylesOption.clientData = (ClientData)sizeof(LineStyle); + return (Element *)elemPtr; +} + +#ifdef notdef +/* + *--------------------------------------------------------------------------- + * + * MapLineProc -- + * + * Calculates the actual window coordinates of the line element. The + * window coordinates are saved in an allocated point array. + * + * Results: + * None. + * + * Side effects: + * Memory is (re)allocated for the point array. + * + *--------------------------------------------------------------------------- + */ +static void +MapLineProc(Graph *graphPtr, Element *basePtr) +{ + LineElement *elemPtr = (LineElement *)basePtr; + MapInfo mi; + int size, np; + LineStyle **styleMap; + Blt_ChainLink link; + + ResetLine(elemPtr); + np = NUMBEROFPOINTS(elemPtr); + if (np < 1) { + return; /* No data points */ + } + GetScreenPoints(graphPtr, elemPtr, &mi); + MapSymbols(graphPtr, elemPtr, &mi); + + if ((elemPtr->flags & ACTIVE_PENDING) && (elemPtr->nActiveIndices > 0)) { + MapActiveSymbols(graphPtr, elemPtr); + } + /* + * Map connecting line segments if they are to be displayed. + */ + elemPtr->smooth = elemPtr->reqSmooth; + if ((np > 1) && ((graphPtr->classId == CID_ELEM_STRIP) || + (elemPtr->builtinPen.traceWidth > 0))) { + + /* + * Do smoothing if necessary. This can extend the coordinate array, + * so both mi.points and mi.nPoints may change. + */ + switch (elemPtr->smooth) { + case PEN_SMOOTH_STEP: + GenerateSteps(&mi); + break; + + case PEN_SMOOTH_NATURAL: + case PEN_SMOOTH_QUADRATIC: + if (mi.nScreenPts < 3) { + /* Can't interpolate with less than three points. */ + elemPtr->smooth = PEN_SMOOTH_LINEAR; + } else { + GenerateSpline(graphPtr, elemPtr, &mi); + } + break; + + case PEN_SMOOTH_CATROM: + if (mi.nScreenPts < 3) { + /* Can't interpolate with less than three points. */ + elemPtr->smooth = PEN_SMOOTH_LINEAR; + } else { + GenerateParametricSpline(graphPtr, elemPtr, &mi); + } + break; + + default: + break; + } + if (elemPtr->rTolerance > 0.0) { + ReducePoints(&mi, elemPtr->rTolerance); + } + if (elemPtr->fillBg != NULL) { + MapFillArea(graphPtr, elemPtr, &mi); + } + if (graphPtr->classId == CID_ELEM_STRIP) { + MapStrip(graphPtr, elemPtr, &mi); + } else { + MapTraces(graphPtr, elemPtr, &mi); + } + } + Blt_Free(mi.screenPts); + Blt_Free(mi.map); + + /* Set the symbol size of all the pen styles. */ + for (link = Blt_Chain_FirstLink(elemPtr->styles); link != NULL; + link = Blt_Chain_NextLink(link)) { + LineStyle *stylePtr; + LinePen *penPtr; + + stylePtr = Blt_Chain_GetValue(link); + penPtr = (LinePen *)stylePtr->penPtr; + size = ScaleSymbol(elemPtr, penPtr->symbol.size); + stylePtr->symbolSize = size; + stylePtr->errorBarCapWidth = (penPtr->errorBarCapWidth > 0) + ? penPtr->errorBarCapWidth : Round(size * 0.6666666); + stylePtr->errorBarCapWidth /= 2; + } + styleMap = (LineStyle **)Blt_StyleMap((Element *)elemPtr); + if (((elemPtr->yHigh.nValues > 0) && (elemPtr->yLow.nValues > 0)) || + ((elemPtr->xHigh.nValues > 0) && (elemPtr->xLow.nValues > 0)) || + (elemPtr->xError.nValues > 0) || (elemPtr->yError.nValues > 0)) { + MapErrorBars(graphPtr, elemPtr, styleMap); + } + MergePens(elemPtr, styleMap); + Blt_Free(styleMap); +} +#endif diff --git a/src/bltGrElemLineSpline.C b/src/bltGrElemLineSpline.C new file mode 100644 index 0000000..3f3b621 --- /dev/null +++ b/src/bltGrElemLineSpline.C @@ -0,0 +1,1399 @@ + +#include "bltInt.h" +#include "bltOp.h" +#include <bltVector.h> + +typedef int (SplineProc)(Point2d origPts[], int nOrigPts, Point2d intpPts[], + int nIntpPts); + +typedef double TriDiagonalMatrix[3]; +typedef struct { + double b, c, d; +} Cubic2D; + +typedef struct { + double b, c, d, e, f; +} Quint2D; + +/* + * Quadratic spline parameters + */ +#define E1 param[0] +#define E2 param[1] +#define V1 param[2] +#define V2 param[3] +#define W1 param[4] +#define W2 param[5] +#define Z1 param[6] +#define Z2 param[7] +#define Y1 param[8] +#define Y2 param[9] + +static Tcl_ObjCmdProc SplineCmd; + +/* + *--------------------------------------------------------------------------- + * + * Search -- + * + * Conducts a binary search for a value. This routine is called + * only if key is between x(0) and x(len - 1). + * + * Results: + * Returns the index of the largest value in xtab for which + * x[i] < key. + * + *--------------------------------------------------------------------------- + */ +static int +Search( + Point2d points[], /* Contains the abscissas of the data + * points of interpolation. */ + int nPoints, /* Dimension of x. */ + double key, /* Value whose relative position in + * x is to be located. */ + int *foundPtr) /* (out) Returns 1 if s is found in + * x and 0 otherwise. */ +{ + int high, low, mid; + + low = 0; + high = nPoints - 1; + + while (high >= low) { + mid = (high + low) / 2; + if (key > points[mid].x) { + low = mid + 1; + } else if (key < points[mid].x) { + high = mid - 1; + } else { + *foundPtr = 1; + return mid; + } + } + *foundPtr = 0; + return low; +} + +/* + *--------------------------------------------------------------------------- + * + * QuadChoose -- + * + * Determines the case needed for the computation of the parame- + * ters of the quadratic spline. + * + * Results: + * Returns a case number (1-4) which controls how the parameters + * of the quadratic spline are evaluated. + * + *--------------------------------------------------------------------------- + */ +static int +QuadChoose( + Point2d *p, /* Coordinates of one of the points of + * interpolation */ + Point2d *q, /* Coordinates of one of the points of + * interpolation */ + double m1, /* Derivative condition at point P */ + double m2, /* Derivative condition at point Q */ + double epsilon) /* Error tolerance used to distinguish + * cases when m1 or m2 is relatively + * close to the slope or twice the + * slope of the line segment joining + * the points P and Q. If + * epsilon is not 0.0, then epsilon + * should be greater than or equal to + * machine epsilon. */ +{ + double slope; + + /* Calculate the slope of the line joining P and Q. */ + slope = (q->y - p->y) / (q->x - p->x); + + if (slope != 0.0) { + double relerr; + double mref, mref1, mref2, prod1, prod2; + + prod1 = slope * m1; + prod2 = slope * m2; + + /* Find the absolute values of the slopes slope, m1, and m2. */ + mref = FABS(slope); + mref1 = FABS(m1); + mref2 = FABS(m2); + + /* + * If the relative deviation of m1 or m2 from slope is less than + * epsilon, then choose case 2 or case 3. + */ + relerr = epsilon * mref; + if ((FABS(slope - m1) > relerr) && (FABS(slope - m2) > relerr) && + (prod1 >= 0.0) && (prod2 >= 0.0)) { + double prod; + + prod = (mref - mref1) * (mref - mref2); + if (prod < 0.0) { + /* + * l1, the line through (x1,y1) with slope m1, and l2, + * the line through (x2,y2) with slope m2, intersect + * at a point whose abscissa is between x1 and x2. + * The abscissa becomes a knot of the spline. + */ + return 1; + } + if (mref1 > (mref * 2.0)) { + if (mref2 <= ((2.0 - epsilon) * mref)) { + return 3; + } + } else if (mref2 <= (mref * 2.0)) { + /* + * Both l1 and l2 cross the line through + * (x1+x2)/2.0,y1 and (x1+x2)/2.0,y2, which is the + * midline of the rectangle formed by P and Q or both + * m1 and m2 have signs different than the sign of + * slope, or one of m1 and m2 has opposite sign from + * slope and l1 and l2 intersect to the left of x1 or + * to the right of x2. The point (x1+x2)/2. is a knot + * of the spline. + */ + return 2; + } else if (mref1 <= ((2.0 - epsilon) * mref)) { + /* + * In cases 3 and 4, sign(m1)=sign(m2)=sign(slope). + * Either l1 or l2 crosses the midline, but not both. + * Choose case 4 if mref1 is greater than + * (2.-epsilon)*mref; otherwise, choose case 3. + */ + return 3; + } + /* + * If neither l1 nor l2 crosses the midline, the spline + * requires two knots between x1 and x2. + */ + return 4; + } else { + /* + * The sign of at least one of the slopes m1 or m2 does not + * agree with the sign of *slope*. + */ + if ((prod1 < 0.0) && (prod2 < 0.0)) { + return 2; + } else if (prod1 < 0.0) { + if (mref2 > ((epsilon + 1.0) * mref)) { + return 1; + } else { + return 2; + } + } else if (mref1 > ((epsilon + 1.0) * mref)) { + return 1; + } else { + return 2; + } + } + } else if ((m1 * m2) >= 0.0) { + return 2; + } else { + return 1; + } +} + +/* + *--------------------------------------------------------------------------- + * + * QuadCases -- + * + * Computes the knots and other parameters of the spline on the + * interval PQ. + * + * + * On input-- + * + * P and Q are the coordinates of the points of interpolation. + * + * m1 is the slope at P. + * + * m2 is the slope at Q. + * + * ncase controls the number and location of the knots. + * + * + * On output-- + * + * (v1,v2),(w1,w2),(z1,z2), and (e1,e2) are the coordinates of + * the knots and other parameters of the spline on P. + * (e1,e2) and Q are used only if ncase=4. + * + *--------------------------------------------------------------------------- + */ +static void +QuadCases(Point2d *p, Point2d *q, double m1, double m2, double param[], + int which) +{ + if ((which == 3) || (which == 4)) { /* Parameters used in both 3 and 4 */ + double mbar1, mbar2, mbar3, c1, d1, h1, j1, k1; + + c1 = p->x + (q->y - p->y) / m1; + d1 = q->x + (p->y - q->y) / m2; + h1 = c1 * 2.0 - p->x; + j1 = d1 * 2.0 - q->x; + mbar1 = (q->y - p->y) / (h1 - p->x); + mbar2 = (p->y - q->y) / (j1 - q->x); + + if (which == 4) { /* Case 4. */ + Y1 = (p->x + c1) / 2.0; + V1 = (p->x + Y1) / 2.0; + V2 = m1 * (V1 - p->x) + p->y; + Z1 = (d1 + q->x) / 2.0; + W1 = (q->x + Z1) / 2.0; + W2 = m2 * (W1 - q->x) + q->y; + mbar3 = (W2 - V2) / (W1 - V1); + Y2 = mbar3 * (Y1 - V1) + V2; + Z2 = mbar3 * (Z1 - V1) + V2; + E1 = (Y1 + Z1) / 2.0; + E2 = mbar3 * (E1 - V1) + V2; + } else { /* Case 3. */ + k1 = (p->y - q->y + q->x * mbar2 - p->x * mbar1) / (mbar2 - mbar1); + if (FABS(m1) > FABS(m2)) { + Z1 = (k1 + p->x) / 2.0; + } else { + Z1 = (k1 + q->x) / 2.0; + } + V1 = (p->x + Z1) / 2.0; + V2 = p->y + m1 * (V1 - p->x); + W1 = (q->x + Z1) / 2.0; + W2 = q->y + m2 * (W1 - q->x); + Z2 = V2 + (W2 - V2) / (W1 - V1) * (Z1 - V1); + } + } else if (which == 2) { /* Case 2. */ + Z1 = (p->x + q->x) / 2.0; + V1 = (p->x + Z1) / 2.0; + V2 = p->y + m1 * (V1 - p->x); + W1 = (Z1 + q->x) / 2.0; + W2 = q->y + m2 * (W1 - q->x); + Z2 = (V2 + W2) / 2.0; + } else { /* Case 1. */ + double ztwo; + + Z1 = (p->y - q->y + m2 * q->x - m1 * p->x) / (m2 - m1); + ztwo = p->y + m1 * (Z1 - p->x); + V1 = (p->x + Z1) / 2.0; + V2 = (p->y + ztwo) / 2.0; + W1 = (Z1 + q->x) / 2.0; + W2 = (ztwo + q->y) / 2.0; + Z2 = V2 + (W2 - V2) / (W1 - V1) * (Z1 - V1); + } +} + +static int +QuadSelect(Point2d *p, Point2d *q, double m1, double m2, double epsilon, + double param[]) +{ + int ncase; + + ncase = QuadChoose(p, q, m1, m2, epsilon); + QuadCases(p, q, m1, m2, param, ncase); + return ncase; +} + +/* + *--------------------------------------------------------------------------- + * + * QuadGetImage -- + * + *--------------------------------------------------------------------------- + */ +INLINE static double +QuadGetImage(double p1, double p2, double p3, double x1, double x2, double x3) +{ + double A, B, C; + double y; + + A = x1 - x2; + B = x2 - x3; + C = x1 - x3; + + y = (p1 * (A * A) + p2 * 2.0 * B * A + p3 * (B * B)) / (C * C); + return y; +} + +/* + *--------------------------------------------------------------------------- + * + * QuadSpline -- + * + * Finds the image of a point in x. + * + * On input + * + * x Contains the value at which the spline is evaluated. + * leftX, leftY + * Coordinates of the left-hand data point used in the + * evaluation of x values. + * rightX, rightY + * Coordinates of the right-hand data point used in the + * evaluation of x values. + * Z1, Z2, Y1, Y2, E2, W2, V2 + * Parameters of the spline. + * ncase Controls the evaluation of the spline by indicating + * whether one or two knots were placed in the interval + * (xtabs,xtabs1). + * + * Results: + * The image of the spline at x. + * + *--------------------------------------------------------------------------- + */ +static void +QuadSpline( + Point2d *intp, /* Value at which spline is evaluated */ + Point2d *left, /* Point to the left of the data point to + * be evaluated */ + Point2d *right, /* Point to the right of the data point to + * be evaluated */ + double param[], /* Parameters of the spline */ + int ncase) /* Controls the evaluation of the + * spline by indicating whether one or + * two knots were placed in the + * interval (leftX,rightX) */ +{ + double y; + + if (ncase == 4) { + /* + * Case 4: More than one knot was placed in the interval. + */ + + /* + * Determine the location of data point relative to the 1st knot. + */ + if (Y1 > intp->x) { + y = QuadGetImage(left->y, V2, Y2, Y1, intp->x, left->x); + } else if (Y1 < intp->x) { + /* + * Determine the location of the data point relative to + * the 2nd knot. + */ + if (Z1 > intp->x) { + y = QuadGetImage(Y2, E2, Z2, Z1, intp->x, Y1); + } else if (Z1 < intp->x) { + y = QuadGetImage(Z2, W2, right->y, right->x, intp->x, Z1); + } else { + y = Z2; + } + } else { + y = Y2; + } + } else { + + /* + * Cases 1, 2, or 3: + * + * Determine the location of the data point relative to the + * knot. + */ + if (Z1 < intp->x) { + y = QuadGetImage(Z2, W2, right->y, right->x, intp->x, Z1); + } else if (Z1 > intp->x) { + y = QuadGetImage(left->y, V2, Z2, Z1, intp->x, left->x); + } else { + y = Z2; + } + } + intp->y = y; +} + +/* + *--------------------------------------------------------------------------- + * + * QuadSlopes -- + * + * Calculates the derivative at each of the data points. The + * slopes computed will insure that an osculatory quadratic + * spline will have one additional knot between two adjacent + * points of interpolation. Convexity and monotonicity are + * preserved wherever these conditions are compatible with the + * data. + * + * Results: + * The output array "m" is filled with the derivates at each + * data point. + * + *--------------------------------------------------------------------------- + */ +static void +QuadSlopes(Point2d *points, double *m, int nPoints) +{ + double xbar, xmid, xhat, ydif1, ydif2; + double yxmid; + double m1, m2; + double m1s, m2s; + int i, n, l; + + m1s = m2s = m1 = m2 = 0; + for (l = 0, i = 1, n = 2; i < (nPoints - 1); l++, i++, n++) { + /* + * Calculate the slopes of the two lines joining three + * consecutive data points. + */ + ydif1 = points[i].y - points[l].y; + ydif2 = points[n].y - points[i].y; + m1 = ydif1 / (points[i].x - points[l].x); + m2 = ydif2 / (points[n].x - points[i].x); + if (i == 1) { + m1s = m1, m2s = m2; /* Save slopes of starting point */ + } + /* + * If one of the preceding slopes is zero or if they have opposite + * sign, assign the value zero to the derivative at the middle + * point. + */ + if ((m1 == 0.0) || (m2 == 0.0) || ((m1 * m2) <= 0.0)) { + m[i] = 0.0; + } else if (FABS(m1) > FABS(m2)) { + /* + * Calculate the slope by extending the line with slope m1. + */ + xbar = ydif2 / m1 + points[i].x; + xhat = (xbar + points[n].x) / 2.0; + m[i] = ydif2 / (xhat - points[i].x); + } else { + /* + * Calculate the slope by extending the line with slope m2. + */ + xbar = -ydif1 / m2 + points[i].x; + xhat = (points[l].x + xbar) / 2.0; + m[i] = ydif1 / (points[i].x - xhat); + } + } + + /* Calculate the slope at the last point, x(n). */ + i = nPoints - 2; + n = nPoints - 1; + if ((m1 * m2) < 0.0) { + m[n] = m2 * 2.0; + } else { + xmid = (points[i].x + points[n].x) / 2.0; + yxmid = m[i] * (xmid - points[i].x) + points[i].y; + m[n] = (points[n].y - yxmid) / (points[n].x - xmid); + if ((m[n] * m2) < 0.0) { + m[n] = 0.0; + } + } + + /* Calculate the slope at the first point, x(0). */ + if ((m1s * m2s) < 0.0) { + m[0] = m1s * 2.0; + } else { + xmid = (points[0].x + points[1].x) / 2.0; + yxmid = m[1] * (xmid - points[1].x) + points[1].y; + m[0] = (yxmid - points[0].y) / (xmid - points[0].x); + if ((m[0] * m1s) < 0.0) { + m[0] = 0.0; + } + } + +} + +/* + *--------------------------------------------------------------------------- + * + * QuadEval -- + * + * QuadEval controls the evaluation of an osculatory quadratic + * spline. The user may provide his own slopes at the points of + * interpolation or use the subroutine 'QuadSlopes' to calculate + * slopes which are consistent with the shape of the data. + * + * ON INPUT-- + * intpPts must be a nondecreasing vector of points at which the + * spline will be evaluated. + * origPts contains the abscissas of the data points to be + * interpolated. xtab must be increasing. + * y contains the ordinates of the data points to be + * interpolated. + * m contains the slope of the spline at each point of + * interpolation. + * nPoints number of data points (dimension of xtab and y). + * numEval is the number of points of evaluation (dimension of + * xval and yval). + * epsilon is a relative error tolerance used in subroutine + * 'QuadChoose' to distinguish the situation m(i) or + * m(i+1) is relatively close to the slope or twice + * the slope of the linear segment between xtab(i) and + * xtab(i+1). If this situation occurs, roundoff may + * cause a change in convexity or monotonicity of the + * resulting spline and a change in the case number + * provided by 'QuadChoose'. If epsilon is not equal to zero, + * then epsilon should be greater than or equal to machine + * epsilon. + * ON OUTPUT-- + * yval contains the images of the points in xval. + * err is one of the following error codes: + * 0 - QuadEval ran normally. + * 1 - xval(i) is less than xtab(1) for at least one + * i or xval(i) is greater than xtab(num) for at + * least one i. QuadEval will extrapolate to provide + * function values for these abscissas. + * 2 - xval(i+1) < xval(i) for some i. + * + * + * QuadEval calls the following subroutines or functions: + * Search + * QuadCases + * QuadChoose + * QuadSpline + *--------------------------------------------------------------------------- + */ +static int +QuadEval( + Point2d origPts[], + int nOrigPts, + Point2d intpPts[], + int nIntpPts, + double *m, /* Slope of the spline at each point + * of interpolation. */ + double epsilon) /* Relative error tolerance (see choose) */ +{ + int error; + int i, j; + double param[10]; + int ncase; + int start, end; + int l, p; + int n; + int found; + + /* Initialize indices and set error result */ + error = 0; + l = nOrigPts - 1; + p = l - 1; + ncase = 1; + + /* + * Determine if abscissas of new vector are non-decreasing. + */ + for (j = 1; j < nIntpPts; j++) { + if (intpPts[j].x < intpPts[j - 1].x) { + return 2; + } + } + /* + * Determine if any of the points in xval are LESS than the + * abscissa of the first data point. + */ + for (start = 0; start < nIntpPts; start++) { + if (intpPts[start].x >= origPts[0].x) { + break; + } + } + /* + * Determine if any of the points in xval are GREATER than the + * abscissa of the l data point. + */ + for (end = nIntpPts - 1; end >= 0; end--) { + if (intpPts[end].x <= origPts[l].x) { + break; + } + } + + if (start > 0) { + error = 1; /* Set error value to indicate that + * extrapolation has occurred. */ + /* + * Calculate the images of points of evaluation whose abscissas + * are less than the abscissa of the first data point. + */ + ncase = QuadSelect(origPts, origPts + 1, m[0], m[1], epsilon, param); + for (j = 0; j < (start - 1); j++) { + QuadSpline(intpPts + j, origPts, origPts + 1, param, ncase); + } + if (nIntpPts == 1) { + return error; + } + } + if ((nIntpPts == 1) && (end != (nIntpPts - 1))) { + goto noExtrapolation; + } + + /* + * Search locates the interval in which the first in-range + * point of evaluation lies. + */ + + i = Search(origPts, nOrigPts, intpPts[start].x, &found); + + n = i + 1; + if (n >= nOrigPts) { + n = nOrigPts - 1; + i = nOrigPts - 2; + } + /* + * If the first in-range point of evaluation is equal to one + * of the data points, assign the appropriate value from y. + * Continue until a point of evaluation is found which is not + * equal to a data point. + */ + if (found) { + do { + intpPts[start].y = origPts[i].y; + start++; + if (start >= nIntpPts) { + return error; + } + } while (intpPts[start - 1].x == intpPts[start].x); + + for (;;) { + if (intpPts[start].x < origPts[n].x) { + break; /* Break out of for-loop */ + } + if (intpPts[start].x == origPts[n].x) { + do { + intpPts[start].y = origPts[n].y; + start++; + if (start >= nIntpPts) { + return error; + } + } while (intpPts[start].x == intpPts[start - 1].x); + } + i++; + n++; + } + } + /* + * Calculate the images of all the points which lie within + * range of the data. + */ + if ((i > 0) || (error != 1)) { + ncase = QuadSelect(origPts + i, origPts + n, m[i], m[n], + epsilon, param); + } + for (j = start; j <= end; j++) { + /* + * If xx(j) - x(n) is negative, do not recalculate + * the parameters for this section of the spline since + * they are already known. + */ + if (intpPts[j].x == origPts[n].x) { + intpPts[j].y = origPts[n].y; + continue; + } else if (intpPts[j].x > origPts[n].x) { + double delta; + + /* Determine that the routine is in the correct part of + the spline. */ + do { + i++, n++; + delta = intpPts[j].x - origPts[n].x; + } while (delta > 0.0); + + if (delta < 0.0) { + ncase = QuadSelect(origPts + i, origPts + n, m[i], + m[n], epsilon, param); + } else if (delta == 0.0) { + intpPts[j].y = origPts[n].y; + continue; + } + } + QuadSpline(intpPts + j, origPts + i, origPts + n, param, ncase); + } + + if (end == (nIntpPts - 1)) { + return error; + } + if ((n == l) && (intpPts[end].x != origPts[l].x)) { + goto noExtrapolation; + } + + error = 1; /* Set error value to indicate that + * extrapolation has occurred. */ + ncase = QuadSelect(origPts + p, origPts + l, m[p], m[l], epsilon, param); + + noExtrapolation: + /* + * Calculate the images of the points of evaluation whose + * abscissas are greater than the abscissa of the last data point. + */ + for (j = (end + 1); j < nIntpPts; j++) { + QuadSpline(intpPts + j, origPts + p, origPts + l, param, ncase); + } + return error; +} + +/* + *--------------------------------------------------------------------------- + * + * Shape preserving quadratic splines + * by D.F.Mcallister & J.A.Roulier + * Coded by S.L.Dodd & M.Roulier + * N.C.State University + * + *--------------------------------------------------------------------------- + */ +/* + * Driver routine for quadratic spline package + * On input-- + * X,Y Contain n-long arrays of data (x is increasing) + * XM Contains m-long array of x values (increasing) + * eps Relative error tolerance + * n Number of input data points + * m Number of output data points + * On output-- + * work Contains the value of the first derivative at each data point + * ym Contains the interpolated spline value at each data point + */ +int +Blt_QuadraticSpline(Point2d *origPts, int nOrigPts, Point2d *intpPts, + int nIntpPts) +{ + double epsilon; + double *work; + int result; + + work = Blt_AssertMalloc(nOrigPts * sizeof(double)); + epsilon = 0.0; /* TBA: adjust error via command-line option */ + /* allocate space for vectors used in calculation */ + QuadSlopes(origPts, work, nOrigPts); + result = QuadEval(origPts, nOrigPts, intpPts, nIntpPts, work, epsilon); + Blt_Free(work); + if (result > 1) { + return FALSE; + } + return TRUE; +} + +/* + *--------------------------------------------------------------------------- + * + * Reference: + * Numerical Analysis, R. Burden, J. Faires and A. Reynolds. + * Prindle, Weber & Schmidt 1981 pp 112 + * + * Parameters: + * origPts - vector of points, assumed to be sorted along x. + * intpPts - vector of new points. + * + *--------------------------------------------------------------------------- + */ +int +Blt_NaturalSpline(Point2d *origPts, int nOrigPts, Point2d *intpPts, + int nIntpPts) +{ + Cubic2D *eq; + Point2d *ip, *iend; + TriDiagonalMatrix *A; + double *dx; /* vector of deltas in x */ + double x, dy, alpha; + int isKnot; + int i, j, n; + + dx = Blt_AssertMalloc(sizeof(double) * nOrigPts); + /* Calculate vector of differences */ + for (i = 0, j = 1; j < nOrigPts; i++, j++) { + dx[i] = origPts[j].x - origPts[i].x; + if (dx[i] < 0.0) { + return 0; + } + } + n = nOrigPts - 1; /* Number of intervals. */ + A = Blt_AssertMalloc(sizeof(TriDiagonalMatrix) * nOrigPts); + if (A == NULL) { + Blt_Free(dx); + return 0; + } + /* Vectors to solve the tridiagonal matrix */ + A[0][0] = A[n][0] = 1.0; + A[0][1] = A[n][1] = 0.0; + A[0][2] = A[n][2] = 0.0; + + /* Calculate the intermediate results */ + for (i = 0, j = 1; j < n; j++, i++) { + alpha = 3.0 * ((origPts[j + 1].y / dx[j]) - (origPts[j].y / dx[i]) - + (origPts[j].y / dx[j]) + (origPts[i].y / dx[i])); + A[j][0] = 2 * (dx[j] + dx[i]) - dx[i] * A[i][1]; + A[j][1] = dx[j] / A[j][0]; + A[j][2] = (alpha - dx[i] * A[i][2]) / A[j][0]; + } + + eq = Blt_Malloc(sizeof(Cubic2D) * nOrigPts); + if (eq == NULL) { + Blt_Free(A); + Blt_Free(dx); + return FALSE; + } + eq[0].c = eq[n].c = 0.0; + for (j = n, i = n - 1; i >= 0; i--, j--) { + eq[i].c = A[i][2] - A[i][1] * eq[j].c; + dy = origPts[i+1].y - origPts[i].y; + eq[i].b = (dy) / dx[i] - dx[i] * (eq[j].c + 2.0 * eq[i].c) / 3.0; + eq[i].d = (eq[j].c - eq[i].c) / (3.0 * dx[i]); + } + Blt_Free(A); + Blt_Free(dx); + + /* Now calculate the new values */ + for (ip = intpPts, iend = ip + nIntpPts; ip < iend; ip++) { + ip->y = 0.0; + x = ip->x; + + /* Is it outside the interval? */ + if ((x < origPts[0].x) || (x > origPts[n].x)) { + continue; + } + /* Search for the interval containing x in the point array */ + i = Search(origPts, nOrigPts, x, &isKnot); + if (isKnot) { + ip->y = origPts[i].y; + } else { + i--; + x -= origPts[i].x; + ip->y = origPts[i].y + x * (eq[i].b + x * (eq[i].c + x * eq[i].d)); + } + } + Blt_Free(eq); + return TRUE; +} + +static Blt_OpSpec splineOps[] = +{ + {"natural", 1, Blt_NaturalSpline, 6, 6, "x y splx sply",}, + {"quadratic", 1, Blt_QuadraticSpline, 6, 6, "x y splx sply",}, +}; +static int nSplineOps = sizeof(splineOps) / sizeof(Blt_OpSpec); + +/*ARGSUSED*/ +static int +SplineCmd( + ClientData clientData, /* Not used. */ + Tcl_Interp *interp, + int objc, + Tcl_Obj *const *objv) +{ + SplineProc *proc; + Blt_Vector *x, *y, *splX, *splY; + double *xArr, *yArr; + int i; + Point2d *origPts, *intpPts; + int nOrigPts, nIntpPts; + + proc = Blt_GetOpFromObj(interp, nSplineOps, splineOps, BLT_OP_ARG1, + objc, objv, 0); + if (proc == NULL) { + return TCL_ERROR; + } + if ((Blt_GetVectorFromObj(interp, objv[2], &x) != TCL_OK) || + (Blt_GetVectorFromObj(interp, objv[3], &y) != TCL_OK) || + (Blt_GetVectorFromObj(interp, objv[4], &splX) != TCL_OK)) { + return TCL_ERROR; + } + nOrigPts = Blt_VecLength(x); + if (nOrigPts < 3) { + Tcl_AppendResult(interp, "length of vector \"", Tcl_GetString(objv[2]), + "\" is < 3", (char *)NULL); + return TCL_ERROR; + } + for (i = 1; i < nOrigPts; i++) { + if (Blt_VecData(x)[i] < Blt_VecData(x)[i - 1]) { + Tcl_AppendResult(interp, "x vector \"", Tcl_GetString(objv[2]), + "\" must be monotonically increasing", (char *)NULL); + return TCL_ERROR; + } + } + /* Check that all the data points aren't the same. */ + if (Blt_VecData(x)[i - 1] <= Blt_VecData(x)[0]) { + Tcl_AppendResult(interp, "x vector \"", Tcl_GetString(objv[2]), + "\" must be monotonically increasing", (char *)NULL); + return TCL_ERROR; + } + if (nOrigPts != Blt_VecLength(y)) { + Tcl_AppendResult(interp, "vectors \"", Tcl_GetString(objv[2]), + "\" and \"", Tcl_GetString(objv[3]), + " have different lengths", (char *)NULL); + return TCL_ERROR; + } + nIntpPts = Blt_VecLength(splX); + if (Blt_GetVectorFromObj(interp, objv[5], &splY) != TCL_OK) { + /* + * If the named vector to hold the ordinates of the spline + * doesn't exist, create one the same size as the vector + * containing the abscissas. + */ + if (Blt_CreateVector(interp, Tcl_GetString(objv[5]), nIntpPts, &splY) + != TCL_OK) { + return TCL_ERROR; + } + } else if (nIntpPts != Blt_VecLength(splY)) { + /* + * The x and y vectors differ in size. Make the number of ordinates + * the same as the number of abscissas. + */ + if (Blt_ResizeVector(splY, nIntpPts) != TCL_OK) { + return TCL_ERROR; + } + } + origPts = Blt_Malloc(sizeof(Point2d) * nOrigPts); + if (origPts == NULL) { + Tcl_AppendResult(interp, "can't allocate \"", Blt_Itoa(nOrigPts), + "\" points", (char *)NULL); + return TCL_ERROR; + } + intpPts = Blt_Malloc(sizeof(Point2d) * nIntpPts); + if (intpPts == NULL) { + Tcl_AppendResult(interp, "can't allocate \"", Blt_Itoa(nIntpPts), + "\" points", (char *)NULL); + Blt_Free(origPts); + return TCL_ERROR; + } + xArr = Blt_VecData(x); + yArr = Blt_VecData(y); + for (i = 0; i < nOrigPts; i++) { + origPts[i].x = xArr[i]; + origPts[i].y = yArr[i]; + } + xArr = Blt_VecData(splX); + yArr = Blt_VecData(splY); + for (i = 0; i < nIntpPts; i++) { + intpPts[i].x = xArr[i]; + intpPts[i].y = yArr[i]; + } + if (!(*proc) (origPts, nOrigPts, intpPts, nIntpPts)) { + Tcl_AppendResult(interp, "error generating spline for \"", + Blt_NameOfVector(splY), "\"", (char *)NULL); + Blt_Free(origPts); + Blt_Free(intpPts); + return TCL_ERROR; + } + yArr = Blt_VecData(splY); + for (i = 0; i < nIntpPts; i++) { + yArr[i] = intpPts[i].y; + } + Blt_Free(origPts); + Blt_Free(intpPts); + + /* Finally update the vector. The size of the vector hasn't + * changed, just the data. Reset the vector using TCL_STATIC to + * indicate this. */ + if (Blt_ResetVector(splY, Blt_VecData(splY), Blt_VecLength(splY), + Blt_VecSize(splY), TCL_STATIC) != TCL_OK) { + return TCL_ERROR; + } + return TCL_OK; +} + +int +Blt_SplineCmdInitProc(Tcl_Interp *interp) +{ + static Blt_InitCmdSpec cmdSpec = {"spline", SplineCmd,}; + + return Blt_InitCmd(interp, "::blt", &cmdSpec); +} + + +#define SQR(x) ((x)*(x)) + +typedef struct { + double t; /* Arc length of interval. */ + double x; /* 2nd derivative of X with respect to T */ + double y; /* 2nd derivative of Y with respect to T */ +} CubicSpline; + + +/* + * The following two procedures solve the special linear system which arise + * in cubic spline interpolation. If x is assumed cyclic ( x[i]=x[n+i] ) the + * equations can be written as (i=0,1,...,n-1): + * m[i][0] * x[i-1] + m[i][1] * x[i] + m[i][2] * x[i+1] = b[i] . + * In matrix notation one gets A * x = b, where the matrix A is tridiagonal + * with additional elements in the upper right and lower left position: + * A[i][0] = A_{i,i-1} for i=1,2,...,n-1 and m[0][0] = A_{0,n-1} , + * A[i][1] = A_{i, i } for i=0,1,...,n-1 + * A[i][2] = A_{i,i+1} for i=0,1,...,n-2 and m[n-1][2] = A_{n-1,0}. + * A should be symmetric (A[i+1][0] == A[i][2]) and positive definite. + * The size of the system is given in n (n>=1). + * + * In the first procedure the Cholesky decomposition A = C^T * D * C + * (C is upper triangle with unit diagonal, D is diagonal) is calculated. + * Return TRUE if decomposition exist. + */ +static int +SolveCubic1(TriDiagonalMatrix A[], int n) +{ + int i; + double m_ij, m_n, m_nn, d; + + if (n < 1) { + return FALSE; /* Dimension should be at least 1 */ + } + d = A[0][1]; /* D_{0,0} = A_{0,0} */ + if (d <= 0.0) { + return FALSE; /* A (or D) should be positive definite */ + } + m_n = A[0][0]; /* A_{0,n-1} */ + m_nn = A[n - 1][1]; /* A_{n-1,n-1} */ + for (i = 0; i < n - 2; i++) { + m_ij = A[i][2]; /* A_{i,1} */ + A[i][2] = m_ij / d; /* C_{i,i+1} */ + A[i][0] = m_n / d; /* C_{i,n-1} */ + m_nn -= A[i][0] * m_n; /* to get C_{n-1,n-1} */ + m_n = -A[i][2] * m_n; /* to get C_{i+1,n-1} */ + d = A[i + 1][1] - A[i][2] * m_ij; /* D_{i+1,i+1} */ + if (d <= 0.0) { + return FALSE; /* Elements of D should be positive */ + } + A[i + 1][1] = d; + } + if (n >= 2) { /* Complete last column */ + m_n += A[n - 2][2]; /* add A_{n-2,n-1} */ + A[n - 2][0] = m_n / d; /* C_{n-2,n-1} */ + A[n - 1][1] = d = m_nn - A[n - 2][0] * m_n; /* D_{n-1,n-1} */ + if (d <= 0.0) { + return FALSE; + } + } + return TRUE; +} + +/* + * The second procedure solves the linear system, with the Cholesky + * decomposition calculated above (in m[][]) and the right side b given + * in x[]. The solution x overwrites the right side in x[]. + */ +static void +SolveCubic2(TriDiagonalMatrix A[], CubicSpline spline[], int nIntervals) +{ + int i; + double x, y; + int n, m; + + n = nIntervals - 2; + m = nIntervals - 1; + + /* Division by transpose of C : b = C^{-T} * b */ + x = spline[m].x; + y = spline[m].y; + for (i = 0; i < n; i++) { + spline[i + 1].x -= A[i][2] * spline[i].x; /* C_{i,i+1} * x(i) */ + spline[i + 1].y -= A[i][2] * spline[i].y; /* C_{i,i+1} * x(i) */ + x -= A[i][0] * spline[i].x; /* C_{i,n-1} * x(i) */ + y -= A[i][0] * spline[i].y; /* C_{i,n-1} * x(i) */ + } + if (n >= 0) { + /* C_{n-2,n-1} * x_{n-1} */ + spline[m].x = x - A[n][0] * spline[n].x; + spline[m].y = y - A[n][0] * spline[n].y; + } + /* Division by D: b = D^{-1} * b */ + for (i = 0; i < nIntervals; i++) { + spline[i].x /= A[i][1]; + spline[i].y /= A[i][1]; + } + + /* Division by C: b = C^{-1} * b */ + x = spline[m].x; + y = spline[m].y; + if (n >= 0) { + /* C_{n-2,n-1} * x_{n-1} */ + spline[n].x -= A[n][0] * x; + spline[n].y -= A[n][0] * y; + } + for (i = (n - 1); i >= 0; i--) { + /* C_{i,i+1} * x_{i+1} + C_{i,n-1} * x_{n-1} */ + spline[i].x -= A[i][2] * spline[i + 1].x + A[i][0] * x; + spline[i].y -= A[i][2] * spline[i + 1].y + A[i][0] * y; + } +} + +/* + * Find second derivatives (x''(t_i),y''(t_i)) of cubic spline interpolation + * through list of points (x_i,y_i). The parameter t is calculated as the + * length of the linear stroke. The number of points must be at least 3. + * Note: For CLOSED_CONTOURs the first and last point must be equal. + */ +static CubicSpline * +CubicSlopes( + Point2d points[], + int nPoints, /* Number of points (nPoints>=3) */ + int isClosed, /* CLOSED_CONTOUR or OPEN_CONTOUR */ + double unitX, + double unitY) /* Unit length in x and y (norm=1) */ +{ + CubicSpline *spline; + CubicSpline *s1, *s2; + int n, i; + double norm, dx, dy; + TriDiagonalMatrix *A; /* The tri-diagonal matrix is saved here. */ + + spline = Blt_Malloc(sizeof(CubicSpline) * nPoints); + if (spline == NULL) { + return NULL; + } + A = Blt_Malloc(sizeof(TriDiagonalMatrix) * nPoints); + if (A == NULL) { + Blt_Free(spline); + return NULL; + } + /* + * Calculate first differences in (dxdt2[i], y[i]) and interval lengths + * in dist[i]: + */ + s1 = spline; + for (i = 0; i < nPoints - 1; i++) { + s1->x = points[i+1].x - points[i].x; + s1->y = points[i+1].y - points[i].y; + + /* + * The Norm of a linear stroke is calculated in "normal coordinates" + * and used as interval length: + */ + dx = s1->x / unitX; + dy = s1->y / unitY; + s1->t = sqrt(dx * dx + dy * dy); + + s1->x /= s1->t; /* first difference, with unit norm: */ + s1->y /= s1->t; /* || (dxdt2[i], y[i]) || = 1 */ + s1++; + } + + /* + * Setup linear System: Ax = b + */ + n = nPoints - 2; /* Without first and last point */ + if (isClosed) { + /* First and last points must be equal for CLOSED_CONTOURs */ + spline[nPoints - 1].t = spline[0].t; + spline[nPoints - 1].x = spline[0].x; + spline[nPoints - 1].y = spline[0].y; + n++; /* Add last point (= first point) */ + } + s1 = spline, s2 = s1 + 1; + for (i = 0; i < n; i++) { + /* Matrix A, mainly tridiagonal with cyclic second index + ("j = j+n mod n") + */ + A[i][0] = s1->t; /* Off-diagonal element A_{i,i-1} */ + A[i][1] = 2.0 * (s1->t + s2->t); /* A_{i,i} */ + A[i][2] = s2->t; /* Off-diagonal element A_{i,i+1} */ + + /* Right side b_x and b_y */ + s1->x = (s2->x - s1->x) * 6.0; + s1->y = (s2->y - s1->y) * 6.0; + + /* + * If the linear stroke shows a cusp of more than 90 degree, + * the right side is reduced to avoid oscillations in the + * spline: + */ + /* + * The Norm of a linear stroke is calculated in "normal coordinates" + * and used as interval length: + */ + dx = s1->x / unitX; + dy = s1->y / unitY; + norm = sqrt(dx * dx + dy * dy) / 8.5; + if (norm > 1.0) { + /* The first derivative will not be continuous */ + s1->x /= norm; + s1->y /= norm; + } + s1++, s2++; + } + + if (!isClosed) { + /* Third derivative is set to zero at both ends */ + A[0][1] += A[0][0]; /* A_{0,0} */ + A[0][0] = 0.0; /* A_{0,n-1} */ + A[n-1][1] += A[n-1][2]; /* A_{n-1,n-1} */ + A[n-1][2] = 0.0; /* A_{n-1,0} */ + } + /* Solve linear systems for dxdt2[] and y[] */ + + if (SolveCubic1(A, n)) { /* Cholesky decomposition */ + SolveCubic2(A, spline, n); /* A * dxdt2 = b_x */ + } else { /* Should not happen, but who knows ... */ + Blt_Free(A); + Blt_Free(spline); + return NULL; + } + /* Shift all second derivatives one place right and update the ends. */ + s2 = spline + n, s1 = s2 - 1; + for (/* empty */; s2 > spline; s2--, s1--) { + s2->x = s1->x; + s2->y = s1->y; + } + if (isClosed) { + spline[0].x = spline[n].x; + spline[0].y = spline[n].y; + } else { + /* Third derivative is 0.0 for the first and last interval. */ + spline[0].x = spline[1].x; + spline[0].y = spline[1].y; + spline[n + 1].x = spline[n].x; + spline[n + 1].y = spline[n].y; + } + Blt_Free( A); + return spline; +} + + +/* + * Calculate interpolated values of the spline function (defined via p_cntr + * and the second derivatives dxdt2[] and dydt2[]). The number of tabulated + * values is n. On an equidistant grid n_intpol values are calculated. + */ +static int +CubicEval(Point2d *origPts, int nOrigPts, Point2d *intpPts, int nIntpPts, + CubicSpline *spline) +{ + double t, tSkip, tMax; + Point2d q; + int i, j, count; + + /* Sum the lengths of all the segments (intervals). */ + tMax = 0.0; + for (i = 0; i < nOrigPts - 1; i++) { + tMax += spline[i].t; + } + + /* Need a better way of doing this... */ + + /* The distance between interpolated points */ + tSkip = (1. - 1e-7) * tMax / (nIntpPts - 1); + + t = 0.0; /* Spline parameter value. */ + q = origPts[0]; + count = 0; + + intpPts[count++] = q; /* First point. */ + t += tSkip; + + for (i = 0, j = 1; j < nOrigPts; i++, j++) { + Point2d p; + double d, hx, dx0, dx01, hy, dy0, dy01; + + d = spline[i].t; /* Interval length */ + p = q; + q = origPts[i+1]; + hx = (q.x - p.x) / d; + hy = (q.y - p.y) / d; + dx0 = (spline[j].x + 2 * spline[i].x) / 6.0; + dy0 = (spline[j].y + 2 * spline[i].y) / 6.0; + dx01 = (spline[j].x - spline[i].x) / (6.0 * d); + dy01 = (spline[j].y - spline[i].y) / (6.0 * d); + while (t <= spline[i].t) { /* t in current interval ? */ + p.x += t * (hx + (t - d) * (dx0 + t * dx01)); + p.y += t * (hy + (t - d) * (dy0 + t * dy01)); + intpPts[count++] = p; + t += tSkip; + } + /* Parameter t relative to start of next interval */ + t -= spline[i].t; + } + return count; +} + +/* + * Generate a cubic spline curve through the points (x_i,y_i) which are + * stored in the linked list p_cntr. + * The spline is defined as a 2d-function s(t) = (x(t),y(t)), where the + * parameter t is the length of the linear stroke. + */ +int +Blt_NaturalParametricSpline(Point2d *origPts, int nOrigPts, Region2d *extsPtr, + int isClosed, Point2d *intpPts, int nIntpPts) +{ + double unitX, unitY; /* To define norm (x,y)-plane */ + CubicSpline *spline; + int result; + + if (nOrigPts < 3) { + return 0; + } + if (isClosed) { + origPts[nOrigPts].x = origPts[0].x; + origPts[nOrigPts].y = origPts[0].y; + nOrigPts++; + } + /* Width and height of the grid is used at unit length (2d-norm) */ + unitX = extsPtr->right - extsPtr->left; + unitY = extsPtr->bottom - extsPtr->top; + + if (unitX < FLT_EPSILON) { + unitX = FLT_EPSILON; + } + if (unitY < FLT_EPSILON) { + unitY = FLT_EPSILON; + } + /* Calculate parameters for cubic spline: + * t = arc length of interval. + * dxdt2 = second derivatives of x with respect to t, + * dydt2 = second derivatives of y with respect to t, + */ + spline = CubicSlopes(origPts, nOrigPts, isClosed, unitX, unitY); + if (spline == NULL) { + return 0; + } + result= CubicEval(origPts, nOrigPts, intpPts, nIntpPts, spline); + Blt_Free(spline); + return result; +} + +static INLINE void +CatromCoeffs(Point2d *p, Point2d *a, Point2d *b, Point2d *c, Point2d *d) +{ + a->x = -p[0].x + 3.0 * p[1].x - 3.0 * p[2].x + p[3].x; + b->x = 2.0 * p[0].x - 5.0 * p[1].x + 4.0 * p[2].x - p[3].x; + c->x = -p[0].x + p[2].x; + d->x = 2.0 * p[1].x; + a->y = -p[0].y + 3.0 * p[1].y - 3.0 * p[2].y + p[3].y; + b->y = 2.0 * p[0].y - 5.0 * p[1].y + 4.0 * p[2].y - p[3].y; + c->y = -p[0].y + p[2].y; + d->y = 2.0 * p[1].y; +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_ParametricCatromSpline -- + * + * Computes a spline based upon the data points, returning a new (larger) + * coordinate array of points. + * + * Results: + * None. + * + *--------------------------------------------------------------------------- + */ +int +Blt_CatromParametricSpline(Point2d *points, int nPoints, Point2d *intpPts, + int nIntpPts) +{ + int i; + Point2d *origPts; + double t; + int interval; + Point2d a, b, c, d; + + assert(nPoints > 0); + + /* + * The spline is computed in screen coordinates instead of data points so + * that we can select the abscissas of the interpolated points from each + * pixel horizontally across the plotting area. + */ + origPts = Blt_AssertMalloc((nPoints + 4) * sizeof(Point2d)); + memcpy(origPts + 1, points, sizeof(Point2d) * nPoints); + + origPts[0] = origPts[1]; + origPts[nPoints + 2] = origPts[nPoints + 1] = origPts[nPoints]; + + for (i = 0; i < nIntpPts; i++) { + interval = (int)intpPts[i].x; + t = intpPts[i].y; + assert(interval < nPoints); + CatromCoeffs(origPts + interval, &a, &b, &c, &d); + intpPts[i].x = (d.x + t * (c.x + t * (b.x + t * a.x))) / 2.0; + intpPts[i].y = (d.y + t * (c.y + t * (b.y + t * a.y))) / 2.0; + } + Blt_Free(origPts); + return 1; +} diff --git a/src/bltGrElemOp.C b/src/bltGrElemOp.C new file mode 100644 index 0000000..6f509ac --- /dev/null +++ b/src/bltGrElemOp.C @@ -0,0 +1,2456 @@ + +/* + * bltGrElem.c -- + * + * This module implements generic elements for the BLT graph widget. + * + * Copyright 1993-2004 George A Howlett. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include "bltGraph.h" +#include "bltOp.h" +#include "bltChain.h" +#include <X11/Xutil.h> +#include <bltDataTable.h> + +#define GRAPH_KEY "BLT Graph Data" + +/* Ignore elements that aren't in the display list or have been deleted. */ +#define IGNORE_ELEMENT(e) (((e)->link == NULL) || ((e)->flags & DELETE_PENDING)) + +typedef struct { + Blt_Table table; + int refCount; +} TableClient; + +static Blt_OptionParseProc ObjToAlong; +static Blt_OptionPrintProc AlongToObj; +static Blt_CustomOption alongOption = +{ + ObjToAlong, AlongToObj, NULL, (ClientData)0 +}; +static Blt_OptionFreeProc FreeValues; +static Blt_OptionParseProc ObjToValues; +static Blt_OptionPrintProc ValuesToObj; +Blt_CustomOption bltValuesOption = +{ + ObjToValues, ValuesToObj, FreeValues, (ClientData)0 +}; +static Blt_OptionFreeProc FreeValuePairs; +static Blt_OptionParseProc ObjToValuePairs; +static Blt_OptionPrintProc ValuePairsToObj; +Blt_CustomOption bltValuePairsOption = +{ + ObjToValuePairs, ValuePairsToObj, FreeValuePairs, (ClientData)0 +}; + +static Blt_OptionFreeProc FreeStyles; +static Blt_OptionParseProc ObjToStyles; +static Blt_OptionPrintProc StylesToObj; +Blt_CustomOption bltLineStylesOption = +{ + ObjToStyles, StylesToObj, FreeStyles, (ClientData)0, +}; + +Blt_CustomOption bltBarStylesOption = +{ + ObjToStyles, StylesToObj, FreeStyles, (ClientData)0, +}; + +#include "bltGrElem.h" + +static Blt_VectorChangedProc VectorChangedProc; + +static void FindRange(ElemValues *valuesPtr); +static void FreeDataValues(ElemValues *valuesPtr); +static Tcl_FreeProc FreeElement; + +typedef int (GraphElementProc)(Graph *graphPtr, Tcl_Interp *interp, int objc, + Tcl_Obj *const *objv); + +/* + *--------------------------------------------------------------------------- + * + * Blt_DestroyTableClients -- + * + *--------------------------------------------------------------------------- + */ +/* ARGSUSED */ +void +Blt_DestroyTableClients(Graph *graphPtr) +{ + Blt_HashEntry *hPtr; + Blt_HashSearch iter; + + for (hPtr = Blt_FirstHashEntry(&graphPtr->dataTables, &iter); + hPtr != NULL; hPtr = Blt_NextHashEntry(&iter)) { + TableClient *clientPtr; + + clientPtr = Blt_GetHashValue(hPtr); + if (clientPtr->table != NULL) { + Blt_Table_Close(clientPtr->table); + } + Blt_Free(clientPtr); + } + Blt_DeleteHashTable(&graphPtr->dataTables); +} + + +/* + *--------------------------------------------------------------------------- + * Custom option parse and print procedures + *--------------------------------------------------------------------------- + */ +static int +GetPenStyleFromObj( + Tcl_Interp *interp, + Graph *graphPtr, + Tcl_Obj *objPtr, + ClassId classId, + PenStyle *stylePtr) +{ + Pen *penPtr; + Tcl_Obj **objv; + int objc; + + if (Tcl_ListObjGetElements(interp, objPtr, &objc, &objv) != TCL_OK) { + return TCL_ERROR; + } + if ((objc != 1) && (objc != 3)) { + if (interp != NULL) { + Tcl_AppendResult(interp, "bad style entry \"", + Tcl_GetString(objPtr), + "\": should be \"penName\" or \"penName min max\"", + (char *)NULL); + } + return TCL_ERROR; + } + if (Blt_GetPenFromObj(interp, graphPtr, objv[0], classId, &penPtr) + != TCL_OK) { + return TCL_ERROR; + } + if (objc == 3) { + double min, max; + + if ((Tcl_GetDoubleFromObj(interp, objv[1], &min) != TCL_OK) || + (Tcl_GetDoubleFromObj(interp, objv[2], &max) != TCL_OK)) { + return TCL_ERROR; + } + SetWeight(stylePtr->weight, min, max); + } + stylePtr->penPtr = penPtr; + return TCL_OK; +} + +static void +FreeVectorSource(ElemValues *valuesPtr) +{ + if (valuesPtr->vectorSource.vector != NULL) { + Blt_SetVectorChangedProc(valuesPtr->vectorSource.vector, NULL, NULL); + Blt_FreeVectorId(valuesPtr->vectorSource.vector); + valuesPtr->vectorSource.vector = NULL; + } +} + +static int +FetchVectorValues(Tcl_Interp *interp, ElemValues *valuesPtr, Blt_Vector *vector) +{ + double *array; + + if (valuesPtr->values == NULL) { + array = Blt_Malloc(Blt_VecLength(vector) * sizeof(double)); + } else { + array = Blt_Realloc(valuesPtr->values, + Blt_VecLength(vector) * sizeof(double)); + } + if (array == NULL) { + if (interp != NULL) { + Tcl_AppendResult(interp, "can't allocate new vector", (char *)NULL); + } + return TCL_ERROR; + } + memcpy(array, Blt_VecData(vector), sizeof(double) * Blt_VecLength(vector)); + valuesPtr->min = Blt_VecMin(vector); + valuesPtr->max = Blt_VecMax(vector); + valuesPtr->values = array; + valuesPtr->nValues = Blt_VecLength(vector); + /* FindRange(valuesPtr); */ + return TCL_OK; +} + + +/* + *--------------------------------------------------------------------------- + * + * VectorChangedProc -- + * + * Results: + * None. + * + * Side Effects: + * Graph is redrawn. + * + *--------------------------------------------------------------------------- + */ +static void +VectorChangedProc( + Tcl_Interp *interp, + ClientData clientData, + Blt_VectorNotify notify) +{ + ElemValues *valuesPtr = clientData; + + if (notify == BLT_VECTOR_NOTIFY_DESTROY) { + FreeDataValues(valuesPtr); + } else { + Blt_Vector *vector; + + Blt_GetVectorById(interp, valuesPtr->vectorSource.vector, &vector); + if (FetchVectorValues(NULL, valuesPtr, vector) != TCL_OK) { + return; + } + } + { + Element *elemPtr = valuesPtr->elemPtr; + Graph *graphPtr; + + graphPtr = elemPtr->obj.graphPtr; + graphPtr->flags |= RESET_AXES; + elemPtr->flags |= MAP_ITEM; + if (!IGNORE_ELEMENT(elemPtr)) { + graphPtr->flags |= CACHE_DIRTY; + Blt_EventuallyRedrawGraph(graphPtr); + } + } +} + +static int +GetVectorData(Tcl_Interp *interp, ElemValues *valuesPtr, const char *vecName) +{ + Blt_Vector *vecPtr; + VectorDataSource *srcPtr; + + srcPtr = &valuesPtr->vectorSource; + srcPtr->vector = Blt_AllocVectorId(interp, vecName); + if (Blt_GetVectorById(interp, srcPtr->vector, &vecPtr) != TCL_OK) { + return TCL_ERROR; + } + if (FetchVectorValues(interp, valuesPtr, vecPtr) != TCL_OK) { + FreeVectorSource(valuesPtr); + return TCL_ERROR; + } + Blt_SetVectorChangedProc(srcPtr->vector, VectorChangedProc, valuesPtr); + valuesPtr->type = ELEM_SOURCE_VECTOR; + return TCL_OK; +} + +static int +FetchTableValues(Tcl_Interp *interp, ElemValues *valuesPtr, Blt_TableColumn col) +{ + long i, j; + double *array; + Blt_Table table; + + table = valuesPtr->tableSource.table; + array = Blt_Malloc(sizeof(double) * Blt_Table_NumRows(table)); + if (array == NULL) { + return TCL_ERROR; + } + for (j = 0, i = 1; i <= Blt_Table_NumRows(table); i++) { + Blt_TableRow row; + double value; + + row = Blt_Table_FindRowByIndex(table, i); + value = Blt_Table_GetDouble(table, row, col); + if (FINITE(value)) { + array[j] = value; + j++; + } + } + if (valuesPtr->values != NULL) { + Blt_Free(valuesPtr->values); + } + valuesPtr->nValues = j; + valuesPtr->values = array; + FindRange(valuesPtr); + return TCL_OK; +} + +static void +FreeTableSource(ElemValues *valuesPtr) +{ + TableDataSource *srcPtr; + + srcPtr = &valuesPtr->tableSource; + if (srcPtr->trace != NULL) { + Blt_Table_DeleteTrace(srcPtr->trace); + } + if (srcPtr->notifier != NULL) { + Blt_Table_DeleteNotifier(srcPtr->notifier); + } + if (srcPtr->hashPtr != NULL) { + TableClient *clientPtr; + + clientPtr = Blt_GetHashValue(srcPtr->hashPtr); + clientPtr->refCount--; + if (clientPtr->refCount == 0) { + Graph *graphPtr; + + graphPtr = valuesPtr->elemPtr->obj.graphPtr; + if (srcPtr->table != NULL) { + Blt_Table_Close(srcPtr->table); + } + Blt_Free(clientPtr); + Blt_DeleteHashEntry(&graphPtr->dataTables, srcPtr->hashPtr); + srcPtr->hashPtr = NULL; + } + } +} + +/* + *--------------------------------------------------------------------------- + * + * TableNotifyProc -- + * + * + * Results: + * None. + * + * Side Effects: + * Graph is redrawn. + * + *--------------------------------------------------------------------------- + */ +static int +TableNotifyProc(ClientData clientData, Blt_TableNotifyEvent *eventPtr) +{ + ElemValues *valuesPtr = clientData; + Element *elemPtr; + Graph *graphPtr; + + elemPtr = valuesPtr->elemPtr; + graphPtr = elemPtr->obj.graphPtr; + if ((eventPtr->type == TABLE_NOTIFY_COLUMN_DELETED) || + (FetchTableValues(graphPtr->interp, valuesPtr, + (Blt_TableColumn)eventPtr->header)) != TCL_OK) { + FreeTableSource(valuesPtr); + return TCL_ERROR; + } + /* Always redraw the element. */ + graphPtr->flags |= RESET_AXES; + elemPtr->flags |= MAP_ITEM; + if (!IGNORE_ELEMENT(elemPtr)) { + graphPtr->flags |= CACHE_DIRTY; + Blt_EventuallyRedrawGraph(graphPtr); + } + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * TableTraceProc -- + * + * + * Results: + * None. + * + * Side Effects: + * Graph is redrawn. + * + *--------------------------------------------------------------------------- + */ +static int +TableTraceProc(ClientData clientData, Blt_TableTraceEvent *eventPtr) +{ + ElemValues *valuesPtr = clientData; + Element *elemPtr; + Graph *graphPtr; + + elemPtr = valuesPtr->elemPtr; + graphPtr = elemPtr->obj.graphPtr; + assert((Blt_TableColumn)eventPtr->column == valuesPtr->tableSource.column); + + if (FetchTableValues(eventPtr->interp, valuesPtr, eventPtr->column) + != TCL_OK) { + FreeTableSource(valuesPtr); + return TCL_ERROR; + } + graphPtr->flags |= RESET_AXES; + elemPtr->flags |= MAP_ITEM; + if (!IGNORE_ELEMENT(elemPtr)) { + graphPtr->flags |= CACHE_DIRTY; + Blt_EventuallyRedrawGraph(graphPtr); + } + return TCL_OK; +} + +static int +GetTableData(Tcl_Interp *interp, ElemValues *valuesPtr, const char *tableName, + Tcl_Obj *colObjPtr) +{ + TableDataSource *srcPtr; + TableClient *clientPtr; + int isNew; + Graph *graphPtr; + + memset(&valuesPtr->tableSource, 0, sizeof(TableDataSource)); + srcPtr = &valuesPtr->tableSource; + graphPtr = valuesPtr->elemPtr->obj.graphPtr; + /* See if the graph is already using this table. */ + srcPtr->hashPtr = Blt_CreateHashEntry(&graphPtr->dataTables, tableName, + &isNew); + if (isNew) { + if (Blt_Table_Open(interp, tableName, &srcPtr->table) != TCL_OK) { + return TCL_ERROR; + } + clientPtr = Blt_AssertMalloc(sizeof(TableClient)); + clientPtr->table = srcPtr->table; + clientPtr->refCount = 1; + Blt_SetHashValue(srcPtr->hashPtr, clientPtr); + } else { + clientPtr = Blt_GetHashValue(srcPtr->hashPtr); + srcPtr->table = clientPtr->table; + clientPtr->refCount++; + } + srcPtr->column = Blt_Table_FindColumn(interp, srcPtr->table, colObjPtr); + if (srcPtr->column == NULL) { + goto error; + } + if (FetchTableValues(interp, valuesPtr, srcPtr->column) != TCL_OK) { + goto error; + } + srcPtr->notifier = Blt_Table_CreateColumnNotifier(interp, srcPtr->table, + srcPtr->column, TABLE_NOTIFY_COLUMN_CHANGED, TableNotifyProc, + (Blt_TableNotifierDeleteProc *)NULL, valuesPtr); + srcPtr->trace = Blt_Table_CreateColumnTrace(srcPtr->table, srcPtr->column, + (TABLE_TRACE_WRITES | TABLE_TRACE_UNSETS | TABLE_TRACE_CREATES), TableTraceProc, + (Blt_TableTraceDeleteProc *)NULL, valuesPtr); + valuesPtr->type = ELEM_SOURCE_TABLE; + return TCL_OK; + error: + FreeTableSource(valuesPtr); + return TCL_ERROR; +} + +static int +ParseValues(Tcl_Interp *interp, Tcl_Obj *objPtr, int *nValuesPtr, + double **arrayPtr) +{ + int objc; + Tcl_Obj **objv; + + if (Tcl_ListObjGetElements(interp, objPtr, &objc, &objv) != TCL_OK) { + return TCL_ERROR; + } + *arrayPtr = NULL; + *nValuesPtr = 0; + if (objc > 0) { + double *array; + double *p; + int i; + + array = Blt_Malloc(sizeof(double) * objc); + if (array == NULL) { + Tcl_AppendResult(interp, "can't allocate new vector", (char *)NULL); + return TCL_ERROR; + } + for (p = array, i = 0; i < objc; i++, p++) { + if (Blt_ExprDoubleFromObj(interp, objv[i], p) != TCL_OK) { + Blt_Free(array); + return TCL_ERROR; + } + } + *arrayPtr = array; + *nValuesPtr = objc; + } + return TCL_OK; +} + +static void +FreeDataValues(ElemValues *valuesPtr) +{ + switch (valuesPtr->type) { + case ELEM_SOURCE_VECTOR: + FreeVectorSource(valuesPtr); break; + case ELEM_SOURCE_TABLE: + FreeTableSource(valuesPtr); break; + case ELEM_SOURCE_VALUES: + break; + } + if (valuesPtr->values != NULL) { + Blt_Free(valuesPtr->values); + } + valuesPtr->values = NULL; + valuesPtr->nValues = 0; + valuesPtr->type = ELEM_SOURCE_VALUES; +} + +/* + *--------------------------------------------------------------------------- + * + * FindRange -- + * + * Find the minimum, positive minimum, and maximum values in a given + * vector and store the results in the vector structure. + * + * Results: + * None. + * + * Side Effects: + * Minimum, positive minimum, and maximum values are stored in the + * vector. + * + *--------------------------------------------------------------------------- + */ +static void +FindRange(ElemValues *valuesPtr) +{ + int i; + double *x; + double min, max; + + if ((valuesPtr->nValues < 1) || (valuesPtr->values == NULL)) { + return; /* This shouldn't ever happen. */ + } + x = valuesPtr->values; + + min = DBL_MAX, max = -DBL_MAX; + for(i = 0; i < valuesPtr->nValues; i++) { + if (FINITE(x[i])) { + min = max = x[i]; + break; + } + } + /* Initialize values to track the vector range */ + for (/* empty */; i < valuesPtr->nValues; i++) { + if (FINITE(x[i])) { + if (x[i] < min) { + min = x[i]; + } else if (x[i] > max) { + max = x[i]; + } + } + } + valuesPtr->min = min, valuesPtr->max = max; +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_FindElemValuesMinimum -- + * + * Find the minimum, positive minimum, and maximum values in a given + * vector and store the results in the vector structure. + * + * Results: + * None. + * + * Side Effects: + * Minimum, positive minimum, and maximum values are stored in the + * vector. + * + *--------------------------------------------------------------------------- + */ +double +Blt_FindElemValuesMinimum(ElemValues *valuesPtr, double minLimit) +{ + int i; + double min; + + min = DBL_MAX; + for (i = 0; i < valuesPtr->nValues; i++) { + double x; + + x = valuesPtr->values[i]; + if (x < 0.0) { + /* What do you do about negative values when using log + * scale values seems like a grey area. Mirror. */ + x = -x; + } + if ((x > minLimit) && (min > x)) { + min = x; + } + } + if (min == DBL_MAX) { + min = minLimit; + } + return min; +} + +/*ARGSUSED*/ +static void +FreeValues( + ClientData clientData, /* Not used. */ + Display *display, /* Not used. */ + char *widgRec, + int offset) +{ + ElemValues *valuesPtr = (ElemValues *)(widgRec + offset); + + FreeDataValues(valuesPtr); +} + +/* + *--------------------------------------------------------------------------- + * + * ObjToValues -- + * + * Given a TCL list of numeric expression representing the element + * values, convert into an array of double precision values. In addition, + * the minimum and maximum values are saved. Since elastic values are + * allow (values which translate to the min/max of the graph), we must + * try to get the non-elastic minimum and maximum. + * + * Results: + * The return value is a standard TCL result. The vector is passed + * back via the valuesPtr. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +ObjToValues( + ClientData clientData, /* Not used. */ + Tcl_Interp *interp, /* Interpreter to send results back to */ + Tk_Window tkwin, /* Not used. */ + Tcl_Obj *objPtr, /* TCL list of expressions */ + char *widgRec, /* Element record */ + int offset, /* Offset to field in structure */ + int flags) /* Not used. */ +{ + ElemValues *valuesPtr = (ElemValues *)(widgRec + offset); + Element *elemPtr = (Element *)widgRec; + Tcl_Obj **objv; + int objc; + int result; + const char *string; + + valuesPtr->elemPtr = elemPtr; + if (Tcl_ListObjGetElements(interp, objPtr, &objc, &objv) != TCL_OK) { + return TCL_ERROR; + } + elemPtr->flags |= MAP_ITEM; + + /* Release the current data sources. */ + FreeDataValues(valuesPtr); + if (objc == 0) { + return TCL_OK; /* Empty list of values. */ + } + string = Tcl_GetString(objv[0]); + if ((objc == 1) && (Blt_VectorExists2(interp, string))) { + result = GetVectorData(interp, valuesPtr, string); + } else if ((objc == 2) && (Blt_Table_TableExists(interp, string))) { + result = GetTableData(interp, valuesPtr, string, objv[1]); + } else { + double *values; + int nValues; + + result = ParseValues(interp, objPtr, &nValues, &values); + if (result != TCL_OK) { + return TCL_ERROR; /* Can't parse the values as numbers. */ + } + FreeDataValues(valuesPtr); + if (nValues > 0) { + valuesPtr->values = values; + } + valuesPtr->nValues = nValues; + FindRange(valuesPtr); + valuesPtr->type = ELEM_SOURCE_VALUES; + } + return result; +} + +/* + *--------------------------------------------------------------------------- + * + * ValuesToObj -- + * + * Convert the vector of floating point values into a TCL list. + * + * Results: + * The string representation of the vector is returned. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static Tcl_Obj * +ValuesToObj( + ClientData clientData, /* Not used. */ + Tcl_Interp *interp, + Tk_Window tkwin, /* Not used. */ + char *widgRec, /* Element record */ + int offset, /* Offset to field in structure */ + int flags) /* Not used. */ +{ + ElemValues *valuesPtr = (ElemValues *)(widgRec + offset); + + switch (valuesPtr->type) { + case ELEM_SOURCE_VECTOR: + { + const char *vecName; + + vecName = Blt_NameOfVectorId(valuesPtr->vectorSource.vector); + return Tcl_NewStringObj(vecName, -1); + } + case ELEM_SOURCE_TABLE: + { + Tcl_Obj *listObjPtr; + const char *tableName; + long i; + + listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL); + tableName = Blt_Table_TableName(valuesPtr->tableSource.table); + Tcl_ListObjAppendElement(interp, listObjPtr, + Tcl_NewStringObj(tableName, -1)); + + i = Blt_Table_ColumnIndex(valuesPtr->tableSource.column); + Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewLongObj(i)); + return listObjPtr; + } + case ELEM_SOURCE_VALUES: + { + Tcl_Obj *listObjPtr; + double *vp, *vend; + + listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL); + for (vp = valuesPtr->values, vend = vp + valuesPtr->nValues; + vp < vend; vp++) { + Tcl_ListObjAppendElement(interp, listObjPtr, + Tcl_NewDoubleObj(*vp)); + } + return listObjPtr; + } + default: + abort(); + } + return Tcl_NewStringObj("", 0); +} + +/*ARGSUSED*/ +static void +FreeValuePairs( + ClientData clientData, /* Not used. */ + Display *display, /* Not used. */ + char *widgRec, + int offset) /* Not used. */ +{ + Element *elemPtr = (Element *)widgRec; + + FreeDataValues(&elemPtr->x); + FreeDataValues(&elemPtr->y); +} + + +/* + *--------------------------------------------------------------------------- + * + * ObjToValuePairs -- + * + * This procedure is like ObjToValues except that it interprets + * the list of numeric expressions as X Y coordinate pairs. The + * minimum and maximum for both the X and Y vectors are + * determined. + * + * Results: + * The return value is a standard TCL result. The vectors are + * passed back via the widget record (elemPtr). + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +ObjToValuePairs( + ClientData clientData, /* Not used. */ + Tcl_Interp *interp, /* Interpreter to send results back to */ + Tk_Window tkwin, /* Not used. */ + Tcl_Obj *objPtr, /* TCL list of numeric expressions */ + char *widgRec, /* Element record */ + int offset, /* Not used. */ + int flags) /* Not used. */ +{ + Element *elemPtr = (Element *)widgRec; + double *values; + int nValues; + size_t newSize; + + if (ParseValues(interp, objPtr, &nValues, &values) != TCL_OK) { + return TCL_ERROR; + } + if (nValues & 1) { + Tcl_AppendResult(interp, "odd number of data points", (char *)NULL); + Blt_Free(values); + return TCL_ERROR; + } + nValues /= 2; + newSize = nValues * sizeof(double); + FreeDataValues(&elemPtr->x); /* Release the current data sources. */ + FreeDataValues(&elemPtr->y); + if (newSize > 0) { + double *p; + int i; + + elemPtr->x.values = Blt_AssertMalloc(newSize); + elemPtr->y.values = Blt_AssertMalloc(newSize); + elemPtr->x.nValues = elemPtr->y.nValues = nValues; + for (p = values, i = 0; i < nValues; i++) { + elemPtr->x.values[i] = *p++; + elemPtr->y.values[i] = *p++; + } + Blt_Free(values); + FindRange(&elemPtr->x); + FindRange(&elemPtr->y); + } + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * ValuePairsToObj -- + * + * Convert pairs of floating point values in the X and Y arrays + * into a TCL list. + * + * Results: + * The return value is a string (Tcl list). + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static Tcl_Obj * +ValuePairsToObj( + ClientData clientData, /* Not used. */ + Tcl_Interp *interp, + Tk_Window tkwin, /* Not used. */ + char *widgRec, /* Element information record */ + int offset, /* Not used. */ + int flags) /* Not used. */ +{ + Element *elemPtr = (Element *)widgRec; + Tcl_Obj *listObjPtr; + int i; + int length; + + length = NUMBEROFPOINTS(elemPtr); + listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL); + for (i = 0; i < length; i++) { + Tcl_ListObjAppendElement(interp, listObjPtr, + Tcl_NewDoubleObj(elemPtr->x.values[i])); + Tcl_ListObjAppendElement(interp, listObjPtr, + Tcl_NewDoubleObj(elemPtr->y.values[i])); + } + return listObjPtr; +} + +/* + *--------------------------------------------------------------------------- + * + * ObjToAlong -- + * + * Given a TCL list of numeric expression representing the element + * values, convert into an array of double precision values. In + * addition, the minimum and maximum values are saved. Since + * elastic values are allow (values which translate to the + * min/max of the graph), we must try to get the non-elastic + * minimum and maximum. + * + * Results: + * The return value is a standard TCL result. The vector is passed + * back via the valuesPtr. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +ObjToAlong( + ClientData clientData, /* Not used. */ + Tcl_Interp *interp, /* Interpreter to send results back to */ + Tk_Window tkwin, /* Not used. */ + Tcl_Obj *objPtr, /* String representation of value. */ + char *widgRec, /* Widget record. */ + int offset, /* Offset to field in structure */ + int flags) /* Not used. */ +{ + int *intPtr = (int *)(widgRec + offset); + char *string; + + string = Tcl_GetString(objPtr); + if ((string[0] == 'x') && (string[1] == '\0')) { + *intPtr = SEARCH_X; + } else if ((string[0] == 'y') && (string[1] == '\0')) { + *intPtr = SEARCH_Y; + } else if ((string[0] == 'b') && (strcmp(string, "both") == 0)) { + *intPtr = SEARCH_BOTH; + } else { + Tcl_AppendResult(interp, "bad along value \"", string, "\"", + (char *)NULL); + return TCL_ERROR; + } + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * AlongToObj -- + * + * Convert the vector of floating point values into a TCL list. + * + * Results: + * The string representation of the vector is returned. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static Tcl_Obj * +AlongToObj( + ClientData clientData, /* Not used. */ + Tcl_Interp *interp, /* Not used. */ + Tk_Window tkwin, /* Not used. */ + char *widgRec, /* Widget record */ + int offset, /* Offset to field in structure */ + int flags) /* Not used. */ +{ + int along = *(int *)(widgRec + offset); + Tcl_Obj *objPtr; + + switch (along) { + case SEARCH_X: + objPtr = Tcl_NewStringObj("x", 1); + break; + case SEARCH_Y: + objPtr = Tcl_NewStringObj("y", 1); + break; + case SEARCH_BOTH: + objPtr = Tcl_NewStringObj("both", 4); + break; + default: + objPtr = Tcl_NewStringObj("unknown along value", 4); + break; + } + return objPtr; +} + +void +Blt_FreeStylePalette(Blt_Chain stylePalette) +{ + Blt_ChainLink link; + + /* Skip the first slot. It contains the built-in "normal" pen of + * the element. */ + link = Blt_Chain_FirstLink(stylePalette); + if (link != NULL) { + Blt_ChainLink next; + + for (link = Blt_Chain_NextLink(link); link != NULL; link = next) { + PenStyle *stylePtr; + + next = Blt_Chain_NextLink(link); + stylePtr = Blt_Chain_GetValue(link); + Blt_FreePen(stylePtr->penPtr); + Blt_Chain_DeleteLink(stylePalette, link); + } + } +} + +/*ARGSUSED*/ +static void +FreeStyles( + ClientData clientData, /* Not used. */ + Display *display, /* Not used. */ + char *widgRec, + int offset) +{ + Blt_Chain stylePalette = *(Blt_Chain *)(widgRec + offset); + + Blt_FreeStylePalette(stylePalette); +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_ObjToStyles -- + * + * Parse the list of style names. + * + * Results: + * The return value is a standard TCL result. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +ObjToStyles( + ClientData clientData, /* Not used. */ + Tcl_Interp *interp, /* Interpreter to send results back to */ + Tk_Window tkwin, /* Not used. */ + Tcl_Obj *objPtr, /* String representing style list */ + char *widgRec, /* Element information record */ + int offset, /* Offset to field in structure */ + int flags) /* Not used. */ +{ + Blt_Chain stylePalette = *(Blt_Chain *)(widgRec + offset); + Blt_ChainLink link; + Element *elemPtr = (Element *)(widgRec); + PenStyle *stylePtr; + Tcl_Obj **objv; + int objc; + int i; + size_t size = (size_t)clientData; + + + if (Tcl_ListObjGetElements(interp, objPtr, &objc, &objv) != TCL_OK) { + return TCL_ERROR; + } + /* Reserve the first entry for the "normal" pen. We'll set the + * style later */ + Blt_FreeStylePalette(stylePalette); + link = Blt_Chain_FirstLink(stylePalette); + if (link == NULL) { + link = Blt_Chain_AllocLink(size); + Blt_Chain_LinkAfter(stylePalette, link, NULL); + } + stylePtr = Blt_Chain_GetValue(link); + stylePtr->penPtr = elemPtr->normalPenPtr; + for (i = 0; i < objc; i++) { + link = Blt_Chain_AllocLink(size); + stylePtr = Blt_Chain_GetValue(link); + stylePtr->weight.min = (double)i; + stylePtr->weight.max = (double)i + 1.0; + stylePtr->weight.range = 1.0; + if (GetPenStyleFromObj(interp, elemPtr->obj.graphPtr, objv[i], + elemPtr->obj.classId, (PenStyle *)stylePtr) != TCL_OK) { + Blt_FreeStylePalette(stylePalette); + return TCL_ERROR; + } + Blt_Chain_LinkAfter(stylePalette, link, NULL); + } + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * StylesToObj -- + * + * Convert the style information into a Tcl_Obj. + * + * Results: + * The string representing the style information is returned. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static Tcl_Obj * +StylesToObj( + ClientData clientData, /* Not used. */ + Tcl_Interp *interp, + Tk_Window tkwin, /* Not used. */ + char *widgRec, /* Element information record */ + int offset, /* Offset to field in structure */ + int flags) /* Not used. */ +{ + Blt_Chain stylePalette = *(Blt_Chain *)(widgRec + offset); + Blt_ChainLink link; + Tcl_Obj *listObjPtr; + + listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL); + link = Blt_Chain_FirstLink(stylePalette); + if (link != NULL) { + /* Skip the first style (it's the default) */ + for (link = Blt_Chain_NextLink(link); link != NULL; + link = Blt_Chain_NextLink(link)) { + PenStyle *stylePtr; + Tcl_Obj *subListObjPtr; + + stylePtr = Blt_Chain_GetValue(link); + subListObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL); + Tcl_ListObjAppendElement(interp, subListObjPtr, + Tcl_NewStringObj(stylePtr->penPtr->name, -1)); + Tcl_ListObjAppendElement(interp, subListObjPtr, + Tcl_NewDoubleObj(stylePtr->weight.min)); + Tcl_ListObjAppendElement(interp, subListObjPtr, + Tcl_NewDoubleObj(stylePtr->weight.max)); + Tcl_ListObjAppendElement(interp, listObjPtr, subListObjPtr); + } + } + return listObjPtr; +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_StyleMap -- + * + * Creates an array of style indices and fills it based on the weight + * of each data point. + * + * Results: + * None. + * + * Side effects: + * Memory is freed and allocated for the index array. + * + *--------------------------------------------------------------------------- + */ + +PenStyle ** +Blt_StyleMap(Element *elemPtr) +{ + int i; + int nWeights; /* Number of weights to be examined. + * If there are more data points than + * weights, they will default to the + * normal pen. */ + + PenStyle **dataToStyle; /* Directory of styles. Each array + * element represents the style for + * the data point at that index */ + Blt_ChainLink link; + PenStyle *stylePtr; + double *w; /* Weight vector */ + int nPoints; + + nPoints = NUMBEROFPOINTS(elemPtr); + nWeights = MIN(elemPtr->w.nValues, nPoints); + w = elemPtr->w.values; + link = Blt_Chain_FirstLink(elemPtr->stylePalette); + stylePtr = Blt_Chain_GetValue(link); + + /* + * Create a style mapping array (data point index to style), + * initialized to the default style. + */ + dataToStyle = Blt_AssertMalloc(nPoints * sizeof(PenStyle *)); + for (i = 0; i < nPoints; i++) { + dataToStyle[i] = stylePtr; + } + + for (i = 0; i < nWeights; i++) { + for (link = Blt_Chain_LastLink(elemPtr->stylePalette); link != NULL; + link = Blt_Chain_PrevLink(link)) { + stylePtr = Blt_Chain_GetValue(link); + + if (stylePtr->weight.range > 0.0) { + double norm; + + norm = (w[i] - stylePtr->weight.min) / stylePtr->weight.range; + if (((norm - 1.0) <= DBL_EPSILON) && + (((1.0 - norm) - 1.0) <= DBL_EPSILON)) { + dataToStyle[i] = stylePtr; + break; /* Done: found range that matches. */ + } + } + } + } + return dataToStyle; +} + + +/* + *--------------------------------------------------------------------------- + * + * GetIndex -- + * + * Given a string representing the index of a pair of x,y + * coordinates, return the numeric index. + * + * Results: + * A standard TCL result. + * + *--------------------------------------------------------------------------- + */ +static int +GetIndex(Tcl_Interp *interp, Element *elemPtr, Tcl_Obj *objPtr, int *indexPtr) +{ + char *string; + + string = Tcl_GetString(objPtr); + if ((*string == 'e') && (strcmp("end", string) == 0)) { + *indexPtr = NUMBEROFPOINTS(elemPtr) - 1; + } else if (Blt_ExprIntFromObj(interp, objPtr, indexPtr) != TCL_OK) { + return TCL_ERROR; + } + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_GetElement -- + * + * Find the element represented the given name, returning a pointer to + * its data structure via elemPtrPtr. + * + * Results: + * A standard TCL result. + * + *--------------------------------------------------------------------------- + */ +int +Blt_GetElement(Tcl_Interp *interp, Graph *graphPtr, Tcl_Obj *objPtr, + Element **elemPtrPtr) +{ + Blt_HashEntry *hPtr; + char *name; + + name = Tcl_GetString(objPtr); + hPtr = Blt_FindHashEntry(&graphPtr->elements.table, name); + if (hPtr == NULL) { + if (interp != NULL) { + Tcl_AppendResult(interp, "can't find element \"", name, + "\" in \"", Tk_PathName(graphPtr->tkwin), "\"", (char *)NULL); + } + return TCL_ERROR; + } + *elemPtrPtr = Blt_GetHashValue(hPtr); + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * DestroyElement -- + * + * Add a new element to the graph. + * + * Results: + * The return value is a standard TCL result. + * + *--------------------------------------------------------------------------- + */ +static void +DestroyElement(Element *elemPtr) +{ + Graph *graphPtr = elemPtr->obj.graphPtr; + + Blt_DeleteBindings(graphPtr->bindTable, elemPtr); + Blt_Legend_RemoveElement(graphPtr, elemPtr); + + Blt_FreeOptions(elemPtr->configSpecs, (char *)elemPtr,graphPtr->display, 0); + /* + * Call the element's own destructor to release the memory and + * resources allocated for it. + */ + (*elemPtr->procsPtr->destroyProc) (graphPtr, elemPtr); + + /* Remove it also from the element display list */ + if (elemPtr->link != NULL) { + Blt_Chain_DeleteLink(graphPtr->elements.displayList, elemPtr->link); + if (!IGNORE_ELEMENT(elemPtr)) { + graphPtr->flags |= RESET_WORLD; + Blt_EventuallyRedrawGraph(graphPtr); + } + } + /* Remove the element for the graph's hash table of elements */ + if (elemPtr->hashPtr != NULL) { + Blt_DeleteHashEntry(&graphPtr->elements.table, elemPtr->hashPtr); + } + if (elemPtr->obj.name != NULL) { + Blt_Free(elemPtr->obj.name); + } + if (elemPtr->label != NULL) { + Blt_Free(elemPtr->label); + } + Blt_Free(elemPtr); +} + +static void +FreeElement(DestroyData data) +{ + Element *elemPtr = (Element *)data; + DestroyElement(elemPtr); +} + +/* + *--------------------------------------------------------------------------- + * + * CreateElement -- + * + * Add a new element to the graph. + * + * Results: + * The return value is a standard TCL result. + * + *--------------------------------------------------------------------------- + */ +static int +CreateElement(Graph *graphPtr, Tcl_Interp *interp, int objc, + Tcl_Obj *const *objv, ClassId classId) +{ + Element *elemPtr; + Blt_HashEntry *hPtr; + int isNew; + char *string; + + string = Tcl_GetString(objv[3]); + if (string[0] == '-') { + Tcl_AppendResult(graphPtr->interp, "name of element \"", string, + "\" can't start with a '-'", (char *)NULL); + return TCL_ERROR; + } + hPtr = Blt_CreateHashEntry(&graphPtr->elements.table, string, &isNew); + if (!isNew) { + Tcl_AppendResult(interp, "element \"", string, + "\" already exists in \"", Tcl_GetString(objv[0]), + "\"", (char *)NULL); + return TCL_ERROR; + } + if (classId == CID_ELEM_BAR) { + elemPtr = Blt_BarElement(graphPtr, string, classId); + } else { + /* Stripcharts are line graphs with some options enabled. */ + elemPtr = Blt_LineElement(graphPtr, string, classId); + } + assert(elemPtr->configSpecs != NULL); + elemPtr->hashPtr = hPtr; + Blt_SetHashValue(hPtr, elemPtr); + + if (Blt_ConfigureComponentFromObj(interp, graphPtr->tkwin, + elemPtr->obj.name, "Element", elemPtr->configSpecs, objc - 4, objv + 4, + (char *)elemPtr, 0) != TCL_OK) { + DestroyElement(elemPtr); + return TCL_ERROR; + } + (*elemPtr->procsPtr->configProc) (graphPtr, elemPtr); + elemPtr->link = Blt_Chain_Append(graphPtr->elements.displayList, elemPtr); + graphPtr->flags |= CACHE_DIRTY; + Blt_EventuallyRedrawGraph(graphPtr); + elemPtr->flags |= MAP_ITEM; + graphPtr->flags |= RESET_AXES; + Tcl_SetObjResult(interp, objv[3]); + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_DestroyElements -- + * + * Removes all the graph's elements. This routine is called when + * the graph is destroyed. + * + * Results: + * None. + * + * Side effects: + * Memory allocated for the graph's elements is freed. + * + *--------------------------------------------------------------------------- + */ +void +Blt_DestroyElements(Graph *graphPtr) +{ + Blt_HashEntry *hPtr; + Blt_HashSearch iter; + Element *elemPtr; + + for (hPtr = Blt_FirstHashEntry(&graphPtr->elements.table, &iter); + hPtr != NULL; hPtr = Blt_NextHashEntry(&iter)) { + elemPtr = Blt_GetHashValue(hPtr); + elemPtr->hashPtr = NULL; + DestroyElement(elemPtr); + } + Blt_DeleteHashTable(&graphPtr->elements.table); + Blt_DeleteHashTable(&graphPtr->elements.tagTable); + Blt_Chain_Destroy(graphPtr->elements.displayList); +} + +void +Blt_ConfigureElements(Graph *graphPtr) +{ + Blt_ChainLink link; + + for (link = Blt_Chain_FirstLink(graphPtr->elements.displayList); + link != NULL; link = Blt_Chain_NextLink(link)) { + Element *elemPtr; + + elemPtr = Blt_Chain_GetValue(link); + (*elemPtr->procsPtr->configProc) (graphPtr, elemPtr); + } +} + +void +Blt_MapElements(Graph *graphPtr) +{ + Blt_ChainLink link; + + if (graphPtr->mode != BARS_INFRONT) { + Blt_ResetBarGroups(graphPtr); + } + for (link = Blt_Chain_FirstLink(graphPtr->elements.displayList); + link != NULL; link = Blt_Chain_NextLink(link)) { + Element *elemPtr; + + elemPtr = Blt_Chain_GetValue(link); + if (IGNORE_ELEMENT(elemPtr)) { + continue; + } + if ((graphPtr->flags & MAP_ALL) || (elemPtr->flags & MAP_ITEM)) { + (*elemPtr->procsPtr->mapProc) (graphPtr, elemPtr); + elemPtr->flags &= ~MAP_ITEM; + } + } +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_DrawElements -- + * + * Calls the individual element drawing routines for each + * element. + * + * Results: + * None + * + * Side Effects: + * Elements are drawn into the drawable (pixmap) which will + * eventually be displayed in the graph window. + * + *--------------------------------------------------------------------------- + */ +void +Blt_DrawElements(Graph *graphPtr, Drawable drawable) +{ + Blt_ChainLink link; + + /* Draw with respect to the stacking order. */ + for (link = Blt_Chain_LastLink(graphPtr->elements.displayList); + link != NULL; link = Blt_Chain_PrevLink(link)) { + Element *elemPtr; + + elemPtr = Blt_Chain_GetValue(link); + if ((elemPtr->flags & (HIDE|DELETE_PENDING)) == 0) { + (*elemPtr->procsPtr->drawNormalProc)(graphPtr, drawable, elemPtr); + } + } +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_DrawActiveElements -- + * + * Calls the individual element drawing routines to display + * the active colors for each element. + * + * Results: + * None + * + * Side Effects: + * Elements are drawn into the drawable (pixmap) which will + * eventually be displayed in the graph window. + * + *--------------------------------------------------------------------------- + */ +void +Blt_DrawActiveElements(Graph *graphPtr, Drawable drawable) +{ + Blt_ChainLink link; + + for (link = Blt_Chain_LastLink(graphPtr->elements.displayList); + link != NULL; link = Blt_Chain_PrevLink(link)) { + Element *elemPtr; + + elemPtr = Blt_Chain_GetValue(link); + if ((elemPtr->flags & (HIDE|ACTIVE|DELETE_PENDING)) == ACTIVE) { + (*elemPtr->procsPtr->drawActiveProc)(graphPtr, drawable, elemPtr); + } + } +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_ElementsToPostScript -- + * + * Generates PostScript output for each graph element in the + * element display list. + * + *--------------------------------------------------------------------------- + */ +void +Blt_ElementsToPostScript(Graph *graphPtr, Blt_Ps ps) +{ + Blt_ChainLink link; + + for (link = Blt_Chain_LastLink(graphPtr->elements.displayList); + link != NULL; link = Blt_Chain_PrevLink(link)) { + Element *elemPtr; + + elemPtr = Blt_Chain_GetValue(link); + if (elemPtr->flags & (HIDE|DELETE_PENDING)) { + continue; + } + /* Comment the PostScript to indicate the start of the element */ + Blt_Ps_Format(ps, "\n%% Element \"%s\"\n\n", elemPtr->obj.name); + (*elemPtr->procsPtr->printNormalProc) (graphPtr, ps, elemPtr); + } +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_ActiveElementsToPostScript -- + * + *--------------------------------------------------------------------------- + */ +void +Blt_ActiveElementsToPostScript( Graph *graphPtr, Blt_Ps ps) +{ + Blt_ChainLink link; + + for (link = Blt_Chain_LastLink(graphPtr->elements.displayList); + link != NULL; link = Blt_Chain_PrevLink(link)) { + Element *elemPtr; + + elemPtr = Blt_Chain_GetValue(link); + if ((elemPtr->flags & (DELETE_PENDING|HIDE|ACTIVE)) == ACTIVE) { + Blt_Ps_Format(ps, "\n%% Active Element \"%s\"\n\n", + elemPtr->obj.name); + (*elemPtr->procsPtr->printActiveProc)(graphPtr, ps, elemPtr); + } + } +} + +/* + *--------------------------------------------------------------------------- + * + * ActivateOp -- + * + * Marks data points of elements (given by their index) as active. + * + * Results: + * Returns TCL_OK if no errors occurred. + * + *--------------------------------------------------------------------------- + */ +static int +ActivateOp( + Graph *graphPtr, /* Graph widget */ + Tcl_Interp *interp, /* Interpreter to report errors to */ + int objc, /* Number of element names */ + Tcl_Obj *const *objv) /* List of element names */ +{ + Element *elemPtr; + int i; + int *indices; + int nIndices; + + if (objc == 3) { + Blt_HashEntry *hPtr; + Blt_HashSearch iter; + Tcl_Obj *listObjPtr; + + listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL); + /* List all the currently active elements */ + for (hPtr = Blt_FirstHashEntry(&graphPtr->elements.table, &iter); + hPtr != NULL; hPtr = Blt_NextHashEntry(&iter)) { + elemPtr = Blt_GetHashValue(hPtr); + if (elemPtr->flags & ACTIVE) { + Tcl_ListObjAppendElement(interp, listObjPtr, + Tcl_NewStringObj(elemPtr->obj.name, -1)); + } + } + Tcl_SetObjResult(interp, listObjPtr); + return TCL_OK; + } + if (Blt_GetElement(interp, graphPtr, objv[3], &elemPtr) != TCL_OK) { + return TCL_ERROR; /* Can't find named element */ + } + elemPtr->flags |= ACTIVE | ACTIVE_PENDING; + + indices = NULL; + nIndices = -1; + if (objc > 4) { + int *activePtr; + + nIndices = objc - 4; + activePtr = indices = Blt_AssertMalloc(sizeof(int) * nIndices); + for (i = 4; i < objc; i++) { + if (GetIndex(interp, elemPtr, objv[i], activePtr) != TCL_OK) { + return TCL_ERROR; + } + activePtr++; + } + } + if (elemPtr->activeIndices != NULL) { + Blt_Free(elemPtr->activeIndices); + } + elemPtr->nActiveIndices = nIndices; + elemPtr->activeIndices = indices; + Blt_EventuallyRedrawGraph(graphPtr); + return TCL_OK; +} + +ClientData +Blt_MakeElementTag(Graph *graphPtr, const char *tagName) +{ + Blt_HashEntry *hPtr; + int isNew; + + hPtr = Blt_CreateHashEntry(&graphPtr->elements.tagTable, tagName, &isNew); + return Blt_GetHashKey(&graphPtr->elements.tagTable, hPtr); +} + +/* + *--------------------------------------------------------------------------- + * + * BindOp -- + * + * .g element bind elemName sequence command + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +BindOp( + Graph *graphPtr, + Tcl_Interp *interp, + int objc, + Tcl_Obj *const *objv) +{ + if (objc == 3) { + Blt_HashEntry *hPtr; + Blt_HashSearch iter; + char *tagName; + Tcl_Obj *listObjPtr; + + listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL); + for (hPtr = Blt_FirstHashEntry(&graphPtr->elements.tagTable, &iter); + hPtr != NULL; hPtr = Blt_NextHashEntry(&iter)) { + tagName = Blt_GetHashKey(&graphPtr->elements.tagTable, hPtr); + Tcl_ListObjAppendElement(interp, listObjPtr, + Tcl_NewStringObj(tagName, -1)); + } + Tcl_SetObjResult(interp, listObjPtr); + return TCL_OK; + } + return Blt_ConfigureBindingsFromObj(interp, graphPtr->bindTable, + Blt_MakeElementTag(graphPtr, Tcl_GetString(objv[3])), + objc - 4, objv + 4); +} + +/* + *--------------------------------------------------------------------------- + * + * CreateOp -- + * + * Add a new element to the graph (using the default type of the + * graph). + * + * Results: + * The return value is a standard TCL result. + * + *--------------------------------------------------------------------------- + */ +static int +CreateOp( + Graph *graphPtr, + Tcl_Interp *interp, + int objc, + Tcl_Obj *const *objv, + ClassId classId) +{ + return CreateElement(graphPtr, interp, objc, objv, classId); +} + +/* + *--------------------------------------------------------------------------- + * + * CgetOp -- + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +CgetOp( + Graph *graphPtr, + Tcl_Interp *interp, + int objc, /* Not used. */ + Tcl_Obj *const *objv) +{ + Element *elemPtr; + + if (Blt_GetElement(interp, graphPtr, objv[3], &elemPtr) != TCL_OK) { + return TCL_ERROR; /* Can't find named element */ + } + if (Blt_ConfigureValueFromObj(interp, graphPtr->tkwin, elemPtr->configSpecs, + (char *)elemPtr, objv[4], 0) != TCL_OK) { + return TCL_ERROR; + } + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * ClosestOp -- + * + * Find the element closest to the specified screen coordinates. + * Options: + * -halo Consider points only with this maximum distance + * from the picked coordinate. + * -interpolate Find closest point along element traces, not just + * data points. + * -along + * + * Results: + * A standard TCL result. If an element could be found within + * the halo distance, the interpreter result is "1", otherwise + * "0". If a closest element exists, the designated TCL array + * variable will be set with the following information: + * + * 1) the element name, + * 2) the index of the closest point, + * 3) the distance (in screen coordinates) from the picked X-Y + * coordinate and the closest point, + * 4) the X coordinate (graph coordinate) of the closest point, + * 5) and the Y-coordinate. + * + *--------------------------------------------------------------------------- + */ + +static Blt_ConfigSpec closestSpecs[] = { + {BLT_CONFIG_PIXELS_NNEG, "-halo", (char *)NULL, (char *)NULL, + (char *)NULL, Blt_Offset(ClosestSearch, halo), 0}, + {BLT_CONFIG_BOOLEAN, "-interpolate", (char *)NULL, (char *)NULL, + (char *)NULL, Blt_Offset(ClosestSearch, mode), 0 }, + {BLT_CONFIG_CUSTOM, "-along", (char *)NULL, (char *)NULL, + (char *)NULL, Blt_Offset(ClosestSearch, along), 0, &alongOption}, + {BLT_CONFIG_END, (char *)NULL, (char *)NULL, (char *)NULL, + (char *)NULL, 0, 0} +}; + +static int +ClosestOp( + Graph *graphPtr, /* Graph widget */ + Tcl_Interp *interp, /* Interpreter to report results to */ + int objc, /* Number of element names */ + Tcl_Obj *const *objv) /* List of element names */ +{ + Element *elemPtr; + ClosestSearch search; + int i, x, y; + char *string; + + if (graphPtr->flags & RESET_AXES) { + Blt_ResetAxes(graphPtr); + } + if (Tcl_GetIntFromObj(interp, objv[3], &x) != TCL_OK) { + Tcl_AppendResult(interp, ": bad window x-coordinate", (char *)NULL); + return TCL_ERROR; + } + if (Tcl_GetIntFromObj(interp, objv[4], &y) != TCL_OK) { + Tcl_AppendResult(interp, ": bad window y-coordinate", (char *)NULL); + return TCL_ERROR; + } + for (i = 5; i < objc; i += 2) { /* Count switches-value pairs */ + string = Tcl_GetString(objv[i]); + if ((string[0] != '-') || + ((string[1] == '-') && (string[2] == '\0'))) { + break; + } + } + if (i > objc) { + i = objc; + } + + search.mode = SEARCH_POINTS; + search.halo = graphPtr->halo; + search.index = -1; + search.along = SEARCH_BOTH; + search.x = x; + search.y = y; + + if (Blt_ConfigureWidgetFromObj(interp, graphPtr->tkwin, closestSpecs, i - 5, + objv + 5, (char *)&search, BLT_CONFIG_OBJV_ONLY) != TCL_OK) { + return TCL_ERROR; /* Error occurred processing an option. */ + } + if (i < objc) { + string = Tcl_GetString(objv[i]); + if (string[0] == '-') { + i++; /* Skip "--" */ + } + } + search.dist = (double)(search.halo + 1); + + if (i < objc) { + for ( /* empty */ ; i < objc; i++) { + if (Blt_GetElement(interp, graphPtr, objv[i], &elemPtr) != TCL_OK) { + return TCL_ERROR; /* Can't find named element */ + } + if (IGNORE_ELEMENT(elemPtr)) { + continue; + } + if (elemPtr->flags & (HIDE|MAP_ITEM)) { + continue; + } + (*elemPtr->procsPtr->closestProc) (graphPtr, elemPtr, &search); + } + } else { + Blt_ChainLink link; + + /* + * Find the closest point from the set of displayed elements, + * searching the display list from back to front. That way if + * the points from two different elements overlay each other + * exactly, the last one picked will be the topmost. + */ + for (link = Blt_Chain_LastLink(graphPtr->elements.displayList); + link != NULL; link = Blt_Chain_PrevLink(link)) { + elemPtr = Blt_Chain_GetValue(link); + if (elemPtr->flags & (HIDE|MAP_ITEM|DELETE_PENDING)) { + continue; + } + (*elemPtr->procsPtr->closestProc) (graphPtr, elemPtr, &search); + } + } + if (search.dist < (double)search.halo) { + Tcl_Obj *listObjPtr; + /* + * Return a list of name value pairs. + */ + listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL); + Tcl_ListObjAppendElement(interp, listObjPtr, + Tcl_NewStringObj("name", -1)); + Tcl_ListObjAppendElement(interp, listObjPtr, + Tcl_NewStringObj(search.elemPtr->obj.name, -1)); + Tcl_ListObjAppendElement(interp, listObjPtr, + Tcl_NewStringObj("index", -1)); + Tcl_ListObjAppendElement(interp, listObjPtr, + Tcl_NewIntObj(search.index)); + Tcl_ListObjAppendElement(interp, listObjPtr, + Tcl_NewStringObj("x", -1)); + Tcl_ListObjAppendElement(interp, listObjPtr, + Tcl_NewDoubleObj(search.point.x)); + Tcl_ListObjAppendElement(interp, listObjPtr, + Tcl_NewStringObj("y", -1)); + Tcl_ListObjAppendElement(interp, listObjPtr, + Tcl_NewDoubleObj(search.point.y)); + Tcl_ListObjAppendElement(interp, listObjPtr, + Tcl_NewStringObj("dist", -1)); + Tcl_ListObjAppendElement(interp, listObjPtr, + Tcl_NewDoubleObj(search.dist)); + Tcl_SetObjResult(interp, listObjPtr); + } + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * ConfigureOp -- + * + * Sets the element specifications by the given the command line + * arguments and calls the element specification configuration + * routine. If zero or one command line options are given, only + * information about the option(s) is returned in interp->result. + * If the element configuration has changed and the element is + * currently displayed, the axis limits are updated and + * recomputed. + * + * Results: + * The return value is a standard TCL result. + * + * Side Effects: + * Graph will be redrawn to reflect the new display list. + * + *--------------------------------------------------------------------------- + */ +static int +ConfigureOp( + Graph *graphPtr, + Tcl_Interp *interp, + int objc, + Tcl_Obj *const *objv) +{ + int nNames, nOpts; + Tcl_Obj *const *options; + int i; + + /* Figure out where the option value pairs begin */ + objc -= 3; + objv += 3; + for (i = 0; i < objc; i++) { + Element *elemPtr; + char *string; + + string = Tcl_GetString(objv[i]); + if (string[0] == '-') { + break; + } + if (Blt_GetElement(interp, graphPtr, objv[i], &elemPtr) != TCL_OK) { + return TCL_ERROR; /* Can't find named element */ + } + } + nNames = i; /* Number of element names specified */ + nOpts = objc - i; /* Number of options specified */ + options = objv + nNames; /* Start of options in objv */ + + for (i = 0; i < nNames; i++) { + Element *elemPtr; + int flags; + + if (Blt_GetElement(interp, graphPtr, objv[i], &elemPtr) != TCL_OK) { + return TCL_ERROR; + } + flags = BLT_CONFIG_OBJV_ONLY; + if (nOpts == 0) { + return Blt_ConfigureInfoFromObj(interp, graphPtr->tkwin, + elemPtr->configSpecs, (char *)elemPtr, (Tcl_Obj *)NULL, flags); + } else if (nOpts == 1) { + return Blt_ConfigureInfoFromObj(interp, graphPtr->tkwin, + elemPtr->configSpecs, (char *)elemPtr, options[0], flags); + } + if (Blt_ConfigureWidgetFromObj(interp, graphPtr->tkwin, + elemPtr->configSpecs, nOpts, options, (char *)elemPtr, flags) + != TCL_OK) { + return TCL_ERROR; + } + if ((*elemPtr->procsPtr->configProc) (graphPtr, elemPtr) != TCL_OK) { + return TCL_ERROR; /* Failed to configure element */ + } + if (Blt_ConfigModified(elemPtr->configSpecs, "-hide", (char *)NULL)) { + graphPtr->flags |= RESET_AXES; + elemPtr->flags |= MAP_ITEM; + } + /* If data points or axes have changed, reset the axes (may + * affect autoscaling) and recalculate the screen points of + * the element. */ + + if (Blt_ConfigModified(elemPtr->configSpecs, "-*data", "-map*", "-x", + "-y", (char *)NULL)) { + graphPtr->flags |= RESET_WORLD; + elemPtr->flags |= MAP_ITEM; + } + /* The new label may change the size of the legend */ + if (Blt_ConfigModified(elemPtr->configSpecs, "-label", (char *)NULL)) { + graphPtr->flags |= (MAP_WORLD | REDRAW_WORLD); + } + } + /* Update the pixmap if any configuration option changed */ + graphPtr->flags |= CACHE_DIRTY; + Blt_EventuallyRedrawGraph(graphPtr); + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * DeactivateOp -- + * + * Clears the active bit for the named elements. + * + * Results: + * Returns TCL_OK if no errors occurred. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +DeactivateOp( + Graph *graphPtr, /* Graph widget */ + Tcl_Interp *interp, /* Not used. */ + int objc, /* Number of element names */ + Tcl_Obj *const *objv) /* List of element names */ +{ + int i; + + for (i = 3; i < objc; i++) { + Element *elemPtr; + + if (Blt_GetElement(interp, graphPtr, objv[i], &elemPtr) != TCL_OK) { + return TCL_ERROR; /* Can't find named element */ + } + elemPtr->flags &= ~(ACTIVE | ACTIVE_PENDING); + if (elemPtr->activeIndices != NULL) { + Blt_Free(elemPtr->activeIndices); + elemPtr->activeIndices = NULL; + } + elemPtr->nActiveIndices = 0; + } + Blt_EventuallyRedrawGraph(graphPtr); + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * DeleteOp -- + * + * Delete the named elements from the graph. + * + * Results: + * TCL_ERROR is returned if any of the named elements can not be + * found. Otherwise TCL_OK is returned; + * + * Side Effects: + * If the element is currently displayed, the plotting area of + * the graph is redrawn. Memory and resources allocated by the + * elements are released. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +DeleteOp( + Graph *graphPtr, /* Graph widget */ + Tcl_Interp *interp, /* Not used. */ + int objc, /* Number of element names */ + Tcl_Obj *const *objv) /* List of element names */ +{ + int i; + + for (i = 3; i < objc; i++) { + Element *elemPtr; + + if (Blt_GetElement(interp, graphPtr, objv[i], &elemPtr) != TCL_OK) { + return TCL_ERROR; /* Can't find named element */ + } + elemPtr->flags |= DELETE_PENDING; + Tcl_EventuallyFree(elemPtr, FreeElement); + } + Blt_EventuallyRedrawGraph(graphPtr); + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * ExistsOp -- + * + * Indicates if the named element exists in the graph. + * + * Results: + * The return value is a standard TCL result. The interpreter + * result will contain "1" or "0". + * + *--------------------------------------------------------------------------- + */ +/* ARGSUSED */ +static int +ExistsOp( + Graph *graphPtr, + Tcl_Interp *interp, + int objc, /* Not used. */ + Tcl_Obj *const *objv) +{ + Blt_HashEntry *hPtr; + + hPtr = Blt_FindHashEntry(&graphPtr->elements.table, Tcl_GetString(objv[3])); + Tcl_SetBooleanObj(Tcl_GetObjResult(interp), (hPtr != NULL)); + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * GetOp -- + * + * Returns the name of the picked element (using the element + * bind operation). Right now, the only name accepted is + * "current". + * + * Results: + * A standard TCL result. The interpreter result will contain + * the name of the element. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +GetOp( + Graph *graphPtr, + Tcl_Interp *interp, + int objc, /* Not used. */ + Tcl_Obj *const *objv) +{ + char *string; + + string = Tcl_GetString(objv[3]); + if ((string[0] == 'c') && (strcmp(string, "current") == 0)) { + Element *elemPtr; + + elemPtr = Blt_GetCurrentItem(graphPtr->bindTable); + /* Report only on elements. */ + if ((elemPtr != NULL) && ((elemPtr->flags & DELETE_PENDING) == 0) && + (elemPtr->obj.classId >= CID_ELEM_BAR) && + (elemPtr->obj.classId <= CID_ELEM_STRIP)) { + Tcl_SetStringObj(Tcl_GetObjResult(interp), elemPtr->obj.name,-1); + } + } + return TCL_OK; +} + +static Tcl_Obj * +DisplayListObj(Graph *graphPtr) +{ + Tcl_Obj *listObjPtr; + Blt_ChainLink link; + + listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL); + for (link = Blt_Chain_FirstLink(graphPtr->elements.displayList); + link != NULL; link = Blt_Chain_NextLink(link)) { + Element *elemPtr; + Tcl_Obj *objPtr; + + elemPtr = Blt_Chain_GetValue(link); + objPtr = Tcl_NewStringObj(elemPtr->obj.name, -1); + Tcl_ListObjAppendElement(graphPtr->interp, listObjPtr, objPtr); + } + return listObjPtr; +} + +/* + *--------------------------------------------------------------------------- + * + * LowerOp -- + * + * Lowers the named elements to the bottom of the display list. + * + * Results: + * A standard TCL result. The interpreter result will contain the new + * display list of element names. + * + * .g element lower elem ?elem...? + * + *--------------------------------------------------------------------------- + */ +static int +LowerOp(Graph *graphPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) +{ + Blt_Chain chain; + Blt_ChainLink link, next; + int i; + + /* Move the links of lowered elements out of the display list into + * a temporary list. */ + chain = Blt_Chain_Create(); + for (i = 3; i < objc; i++) { + Element *elemPtr; + + if (Blt_GetElement(interp, graphPtr, objv[i], &elemPtr) != TCL_OK) { + return TCL_ERROR; /* Can't find named element */ + } + Blt_Chain_UnlinkLink(graphPtr->elements.displayList, elemPtr->link); + Blt_Chain_LinkAfter(chain, elemPtr->link, NULL); + } + /* Append the links to end of the display list. */ + for (link = Blt_Chain_FirstLink(chain); link != NULL; link = next) { + next = Blt_Chain_NextLink(link); + Blt_Chain_UnlinkLink(chain, link); + Blt_Chain_LinkAfter(graphPtr->elements.displayList, link, NULL); + } + Blt_Chain_Destroy(chain); + Tcl_SetObjResult(interp, DisplayListObj(graphPtr)); + graphPtr->flags |= RESET_WORLD; + Blt_EventuallyRedrawGraph(graphPtr); + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * NamesOp -- + * + * Returns the names of the elements is the graph matching + * one of more patterns provided. If no pattern arguments + * are given, then all element names will be returned. + * + * Results: + * The return value is a standard TCL result. The interpreter + * result will contain a TCL list of the element names. + * + *--------------------------------------------------------------------------- + */ +static int +NamesOp( + Graph *graphPtr, + Tcl_Interp *interp, + int objc, + Tcl_Obj *const *objv) +{ + Tcl_Obj *listObjPtr; + + listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL); + if (objc == 3) { + Blt_HashEntry *hPtr; + Blt_HashSearch iter; + + for (hPtr = Blt_FirstHashEntry(&graphPtr->elements.table, &iter); + hPtr != NULL; hPtr = Blt_NextHashEntry(&iter)) { + Element *elemPtr; + Tcl_Obj *objPtr; + + elemPtr = Blt_GetHashValue(hPtr); + objPtr = Tcl_NewStringObj(elemPtr->obj.name, -1); + Tcl_ListObjAppendElement(interp, listObjPtr, objPtr); + } + } else { + Blt_HashEntry *hPtr; + Blt_HashSearch iter; + + for (hPtr = Blt_FirstHashEntry(&graphPtr->elements.table, &iter); + hPtr != NULL; hPtr = Blt_NextHashEntry(&iter)) { + Element *elemPtr; + int i; + + elemPtr = Blt_GetHashValue(hPtr); + for (i = 3; i < objc; i++) { + if (Tcl_StringMatch(elemPtr->obj.name,Tcl_GetString(objv[i]))) { + Tcl_Obj *objPtr; + + objPtr = Tcl_NewStringObj(elemPtr->obj.name, -1); + Tcl_ListObjAppendElement(interp, listObjPtr, objPtr); + break; + } + } + } + } + Tcl_SetObjResult(interp, listObjPtr); + return TCL_OK; +} + + +/* + *--------------------------------------------------------------------------- + * + * RaiseOp -- + * + * Reset the element within the display list. + * + * Results: + * The return value is a standard TCL result. The interpreter + * result will contain the new display list of element names. + * + * .g element raise ?elem...? + * + *--------------------------------------------------------------------------- + */ +static int +RaiseOp(Graph *graphPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) +{ + Blt_Chain chain; + Blt_ChainLink link, prev; + int i; + + /* Move the links of lowered elements out of the display list into + * a temporary list. */ + chain = Blt_Chain_Create(); + for (i = 3; i < objc; i++) { + Element *elemPtr; + + if (Blt_GetElement(interp, graphPtr, objv[i], &elemPtr) != TCL_OK) { + return TCL_ERROR; /* Can't find named element */ + } + Blt_Chain_UnlinkLink(graphPtr->elements.displayList, elemPtr->link); + Blt_Chain_LinkAfter(chain, elemPtr->link, NULL); + } + /* Prepend the links to beginning of the display list in reverse order. */ + for (link = Blt_Chain_LastLink(chain); link != NULL; link = prev) { + prev = Blt_Chain_PrevLink(link); + Blt_Chain_UnlinkLink(chain, link); + Blt_Chain_LinkBefore(graphPtr->elements.displayList, link, NULL); + } + Blt_Chain_Destroy(chain); + Tcl_SetObjResult(interp, DisplayListObj(graphPtr)); + graphPtr->flags |= RESET_WORLD; + Blt_EventuallyRedrawGraph(graphPtr); + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * ShowOp -- + * + * Queries or resets the element display list. + * + * Results: + * The return value is a standard TCL result. The interpreter + * result will contain the new display list of element names. + * + *--------------------------------------------------------------------------- + */ +static int +ShowOp( + Graph *graphPtr, + Tcl_Interp *interp, + int objc, + Tcl_Obj *const *objv) +{ + if (objc == 4) { + Blt_Chain chain; + Blt_ChainLink link; + Tcl_Obj **elem; + int i, n; + + if (Tcl_ListObjGetElements(interp, objv[3], &n, &elem) != TCL_OK) { + return TCL_ERROR; + } + /* Collect the named elements into a list. */ + chain = Blt_Chain_Create(); + for (i = 0; i < n; i++) { + Element *elemPtr; /* Element information record */ + + if (Blt_GetElement(interp, graphPtr, elem[i], &elemPtr) != TCL_OK) { + Blt_Chain_Destroy(chain); + return TCL_ERROR; + } + Blt_Chain_Append(chain, elemPtr); + } + /* Clear the links from the currently displayed elements. */ + for (link = Blt_Chain_FirstLink(graphPtr->elements.displayList); + link != NULL; link = Blt_Chain_NextLink(link)) { + Element *elemPtr; + + elemPtr = Blt_Chain_GetValue(link); + elemPtr->link = NULL; + } + Blt_Chain_Destroy(graphPtr->elements.displayList); + graphPtr->elements.displayList = chain; + /* Set links on all the displayed elements. */ + for (link = Blt_Chain_FirstLink(chain); link != NULL; + link = Blt_Chain_NextLink(link)) { + Element *elemPtr; + + elemPtr = Blt_Chain_GetValue(link); + elemPtr->link = link; + } + graphPtr->flags |= RESET_WORLD; + Blt_EventuallyRedrawGraph(graphPtr); + } + Tcl_SetObjResult(interp, DisplayListObj(graphPtr)); + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * TypeOp -- + * + * Returns the name of the type of the element given by some + * element name. + * + * Results: + * A standard TCL result. Returns the type of the element in + * interp->result. If the identifier given doesn't represent an + * element, then an error message is left in interp->result. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +TypeOp( + Graph *graphPtr, /* Graph widget */ + Tcl_Interp *interp, + int objc, /* Not used. */ + Tcl_Obj *const *objv) /* Element name */ +{ + Element *elemPtr; + const char *string; + + if (Blt_GetElement(interp, graphPtr, objv[3], &elemPtr) != TCL_OK) { + return TCL_ERROR; /* Can't find named element */ + } + switch (elemPtr->obj.classId) { + case CID_ELEM_BAR: string = "bar"; break; + case CID_ELEM_CONTOUR: string = "contour"; break; + case CID_ELEM_LINE: string = "line"; break; + case CID_ELEM_STRIP: string = "strip"; break; + default: string = "???"; break; + } + Tcl_SetStringObj(Tcl_GetObjResult(interp), string, -1); + return TCL_OK; +} + +/* + * Global routines: + */ +static Blt_OpSpec elemOps[] = { + {"activate", 1, ActivateOp, 3, 0, "?elemName? ?index...?",}, + {"bind", 1, BindOp, 3, 6, "elemName sequence command",}, + {"cget", 2, CgetOp, 5, 5, "elemName option",}, + {"closest", 2, ClosestOp, 5, 0, + "x y ?option value?... ?elemName?...",}, + {"configure", 2, ConfigureOp, 4, 0, + "elemName ?elemName?... ?option value?...",}, + {"create", 2, CreateOp, 4, 0, "elemName ?option value?...",}, + {"deactivate", 3, DeactivateOp, 3, 0, "?elemName?...",}, + {"delete", 3, DeleteOp, 3, 0, "?elemName?...",}, + {"exists", 1, ExistsOp, 4, 4, "elemName",}, + {"get", 1, GetOp, 4, 4, "name",}, + {"lower", 1, LowerOp, 3, 0, "?elemName?...",}, + {"names", 1, NamesOp, 3, 0, "?pattern?...",}, + {"raise", 1, RaiseOp, 3, 0, "?elemName?...",}, + {"show", 1, ShowOp, 3, 4, "?elemList?",}, + {"type", 1, TypeOp, 4, 4, "elemName",}, +}; +static int numElemOps = sizeof(elemOps) / sizeof(Blt_OpSpec); + + +/* + *--------------------------------------------------------------------------- + * + * Blt_ElementOp -- + * + * This procedure is invoked to process the TCL command that + * corresponds to a widget managed by this module. See the user + * documentation for details on what it does. + * + * Results: + * A standard TCL result. + * + * Side effects: + * See the user documentation. + * + *--------------------------------------------------------------------------- + */ +int +Blt_ElementOp( + Graph *graphPtr, /* Graph widget record */ + Tcl_Interp *interp, + int objc, /* # arguments */ + Tcl_Obj *const *objv, /* Argument list */ + ClassId classId) +{ + void *ptr; + int result; + + ptr = Blt_GetOpFromObj(interp, numElemOps, elemOps, BLT_OP_ARG2, + objc, objv, 0); + if (ptr == NULL) { + return TCL_ERROR; + } + if (ptr == CreateOp) { + result = CreateOp(graphPtr, interp, objc, objv, classId); + } else { + GraphElementProc *proc; + + proc = ptr; + result = (*proc) (graphPtr, interp, objc, objv); + } + return result; +} diff --git a/src/bltGrElemOp.h b/src/bltGrElemOp.h new file mode 100644 index 0000000..f0e3969 --- /dev/null +++ b/src/bltGrElemOp.h @@ -0,0 +1,271 @@ + +/* + * bltGrElem.h -- + * + * Copyright 1993-2004 George A Howlett. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef _BLT_GR_ELEM_H +#define _BLT_GR_ELEM_H + +#include <bltVector.h> +#include <bltDataTable.h> + +#define ELEM_SOURCE_VALUES 0 +#define ELEM_SOURCE_VECTOR 1 +#define ELEM_SOURCE_TABLE 2 + +#define SEARCH_X 0 +#define SEARCH_Y 1 +#define SEARCH_BOTH 2 + +#define SHOW_NONE 0 +#define SHOW_X 1 +#define SHOW_Y 2 +#define SHOW_BOTH 3 + +#define SEARCH_POINTS 0 /* Search for closest data point. */ +#define SEARCH_TRACES 1 /* Search for closest point on trace. + * Interpolate the connecting line segments if + * necessary. */ +#define SEARCH_AUTO 2 /* Automatically determine whether to search + * for data points or traces. Look for traces + * if the linewidth is > 0 and if there is + * more than one data point. */ + +#define LABEL_ACTIVE (1<<9) /* Non-zero indicates that the element's entry + * in the legend should be drawn in its active + * foreground and background colors. */ +#define SCALE_SYMBOL (1<<10) + +#define NUMBEROFPOINTS(e) MIN((e)->x.nValues, (e)->y.nValues) + +#define NORMALPEN(e) ((((e)->normalPenPtr == NULL) ? \ + (e)->builtinPenPtr : \ + (e)->normalPenPtr)) + +/* + *--------------------------------------------------------------------------- + * + * Weight -- + * + * Designates a range of values by a minimum and maximum limit. + * + *--------------------------------------------------------------------------- + */ +typedef struct { + double min, max, range; +} Weight; + +#define SetRange(l) \ + ((l).range = ((l).max > (l).min) ? ((l).max - (l).min) : DBL_EPSILON) +#define SetScale(l) \ + ((l).scale = 1.0 / (l).range) +#define SetWeight(l, lo, hi) \ + ((l).min = (lo), (l).max = (hi), SetRange(l)) + +typedef struct { + Segment2d *segments; /* Point to start of this pen's X-error bar + * segments in the element's array. */ + int nSegments; +} ErrorBarSegments; + +/* + * An element has one or more vectors plus several attributes, such as line + * style, thickness, color, and symbol type. It has an identifier which + * distinguishes it among the list of all elements. + */ +typedef struct { + Weight weight; /* Weight range where this pen is valid. */ + Pen *penPtr; /* Pen to use. */ +} PenStyle; + + +typedef struct { + XColor *color; /* Color of error bar */ + int lineWidth; /* Width of the error bar segments. */ + GC gc; + int show; /* Flags for errorbars: none, x, y, or both */ +} ErrorBarAttributes; + +typedef struct { + /* Inputs */ + int halo; /* Maximal screen distance a candidate point + * can be from the sample window coordinate */ + + int mode; /* Indicates whether to find the closest data + * point or the closest point on the trace by + * interpolating the line segments. Can also + * be SEARCH_AUTO, indicating to choose how to + * search.*/ + + int x, y; /* Screen coordinates of test point */ + + int along; /* Indicates to let search run along a + * particular axis: x, y, or both. */ + + /* Outputs */ + Element *elemPtr; /* Name of the closest element */ + + Point2d point; /* Graph coordinates of closest point */ + + int index; /* Index of closest data point */ + + double dist; /* Distance in screen coordinates */ + +} ClosestSearch; + +typedef void (ElementDrawProc) (Graph *graphPtr, Drawable drawable, + Element *elemPtr); + +typedef void (ElementToPostScriptProc) (Graph *graphPtr, Blt_Ps ps, + Element *elemPtr); + +typedef void (ElementDestroyProc) (Graph *graphPtr, Element *elemPtr); + +typedef int (ElementConfigProc) (Graph *graphPtr, Element *elemPtr); + +typedef void (ElementMapProc) (Graph *graphPtr, Element *elemPtr); + +typedef void (ElementExtentsProc) (Element *elemPtr, Region2d *extsPtr); + +typedef void (ElementClosestProc) (Graph *graphPtr, Element *elemPtr, + ClosestSearch *searchPtr); + +typedef void (ElementDrawSymbolProc) (Graph *graphPtr, Drawable drawable, + Element *elemPtr, int x, int y, int symbolSize); + +typedef void (ElementSymbolToPostScriptProc) (Graph *graphPtr, + Blt_Ps ps, Element *elemPtr, double x, double y, int symSize); + +typedef struct { + ElementClosestProc *closestProc; + ElementConfigProc *configProc; + ElementDestroyProc *destroyProc; + ElementDrawProc *drawActiveProc; + ElementDrawProc *drawNormalProc; + ElementDrawSymbolProc *drawSymbolProc; + ElementExtentsProc *extentsProc; + ElementToPostScriptProc *printActiveProc; + ElementToPostScriptProc *printNormalProc; + ElementSymbolToPostScriptProc *printSymbolProc; + ElementMapProc *mapProc; +} ElementProcs; + +typedef struct { + Blt_VectorId vector; +} VectorDataSource; + +typedef struct { + Blt_Table table; /* Data table. */ + Blt_TableColumn column; /* Column of data used. */ + Blt_TableNotifier notifier; /* Notifier used for column destroy + * event. */ + Blt_TableTrace trace; /* Trace used for column + * (set/get/unset). */ + Blt_HashEntry *hashPtr; /* Pointer to the entry of the data + * source in graph's hash table of + * datatables. One graph may use + * multiple columns from the same data + * table. */ +} TableDataSource; + +/* + * The data structure below contains information pertaining to a line vector. + * It consists of an array of floating point data values and for convenience, + * the number and minimum/maximum values. + */ +typedef struct { + int type; /* Selects the type of data populating this + * vector: ELEM_SOURCE_VECTOR, + * ELEM_SOURCE_TABLE, or ELEM_SOURCE_VALUES + */ + Element *elemPtr; /* Element associated with vector. */ + union { + TableDataSource tableSource; + VectorDataSource vectorSource; + }; + double *values; + int nValues; + int arraySize; + double min, max; +} ElemValues; + + +struct _Element { + GraphObj obj; /* Must be first field in element. */ + unsigned int flags; + Blt_HashEntry *hashPtr; + + /* Fields specific to elements. */ + const char *label; /* Label displayed in legend */ + unsigned short row, col; /* Position of the entry in the + * legend. */ + int legendRelief; /* Relief of label in legend. */ + Axis2d axes; /* X-axis and Y-axis mapping the + * element */ + ElemValues x, y, w; /* Contains array of floating point + * graph coordinate values. Also holds + * min/max and the number of + * coordinates */ + int *activeIndices; /* Array of indices (malloc-ed) which + * indicate which data points are + * active (drawn with "active" + * colors). */ + int nActiveIndices; /* Number of active data points. + * Special case: if nActiveIndices < 0 + * and the active bit is set in + * "flags", then all data points are + * drawn active. */ + ElementProcs *procsPtr; + Blt_ConfigSpec *configSpecs; /* Configuration specifications. */ + Pen *activePenPtr; /* Standard Pens */ + Pen *normalPenPtr; + Pen *builtinPenPtr; + Blt_Chain stylePalette; /* Palette of pens. */ + + /* Symbol scaling */ + int scaleSymbols; /* If non-zero, the symbols will scale + * in size as the graph is zoomed + * in/out. */ + double xRange, yRange; /* Initial X-axis and Y-axis ranges: + * used to scale the size of element's + * symbol. */ + int state; + Blt_ChainLink link; /* Element's link in display list. */ +}; + + +BLT_EXTERN double Blt_FindElemValuesMinimum(ElemValues *vecPtr, double minLimit); +BLT_EXTERN void Blt_ResizeStatusArray(Element *elemPtr, int nPoints); +BLT_EXTERN int Blt_GetPenStyle(Graph *graphPtr, char *name, size_t classId, + PenStyle *stylePtr); +BLT_EXTERN void Blt_FreeStylePalette (Blt_Chain stylePalette); +BLT_EXTERN PenStyle **Blt_StyleMap (Element *elemPtr); +BLT_EXTERN void Blt_MapErrorBars(Graph *graphPtr, Element *elemPtr, + PenStyle **dataToStyle); +BLT_EXTERN void Blt_FreeDataValues(ElemValues *evPtr); +BLT_EXTERN int Blt_GetElement(Tcl_Interp *interp, Graph *graphPtr, + Tcl_Obj *objPtr, Element **elemPtrPtr); +BLT_EXTERN void Blt_DestroyTableClients(Graph *graphPtr); + +#endif /* _BLT_GR_ELEM_H */ diff --git a/src/bltGrHairs.C b/src/bltGrHairs.C new file mode 100644 index 0000000..86ebb57 --- /dev/null +++ b/src/bltGrHairs.C @@ -0,0 +1,534 @@ + +/* + * bltGrHairs.c -- + * + * This module implements crosshairs for the BLT graph widget. + * + * Copyright 1993-2004 George A Howlett. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY + * KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS + * OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include "bltGraph.h" +#include "bltOp.h" + +typedef int (GraphCrosshairProc)(Graph *graphPtr, Tcl_Interp *interp, + int objc, Tcl_Obj *const *objv); + +/* + *--------------------------------------------------------------------------- + * + * Crosshairs + * + * Contains the line segments positions and graphics context used + * to simulate crosshairs (by XORing) on the graph. + * + *--------------------------------------------------------------------------- + */ + +struct _Crosshairs { + + XPoint hotSpot; /* Hot spot for crosshairs */ + int visible; /* Internal state of crosshairs. If non-zero, + * crosshairs are displayed. */ + int hidden; /* If non-zero, crosshairs are not displayed. + * This is not necessarily consistent with the + * internal state variable. This is true when + * the hot spot is off the graph. */ + Blt_Dashes dashes; /* Dashstyle of the crosshairs. This represents + * an array of alternatingly drawn pixel + * values. If NULL, the hairs are drawn as a + * solid line */ + int lineWidth; /* Width of the simulated crosshair lines */ + XSegment segArr[2]; /* Positions of line segments representing the + * simulated crosshairs. */ + XColor *colorPtr; /* Foreground color of crosshairs */ + GC gc; /* Graphics context for crosshairs. Set to + * GXxor to not require redraws of graph */ +}; + +#define DEF_HAIRS_DASHES (char *)NULL +#define DEF_HAIRS_FOREGROUND RGB_BLACK +#define DEF_HAIRS_LINE_WIDTH "0" +#define DEF_HAIRS_HIDE "yes" +#define DEF_HAIRS_POSITION (char *)NULL + +BLT_EXTERN Blt_CustomOption bltPointOption; + +static Blt_ConfigSpec configSpecs[] = +{ + {BLT_CONFIG_COLOR, "-color", "color", "Color", DEF_HAIRS_FOREGROUND, + Blt_Offset(Crosshairs, colorPtr), 0}, + {BLT_CONFIG_DASHES, "-dashes", "dashes", "Dashes", DEF_HAIRS_DASHES, + Blt_Offset(Crosshairs, dashes), BLT_CONFIG_NULL_OK}, + {BLT_CONFIG_BOOLEAN, "-hide", "hide", "Hide", DEF_HAIRS_HIDE, + Blt_Offset(Crosshairs, hidden), BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_PIXELS_NNEG, "-linewidth", "lineWidth", "Linewidth", + DEF_HAIRS_LINE_WIDTH, Blt_Offset(Crosshairs, lineWidth), + BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_CUSTOM, "-position", "position", "Position", + DEF_HAIRS_POSITION, Blt_Offset(Crosshairs, hotSpot), 0, &bltPointOption}, + {BLT_CONFIG_END, NULL, NULL, NULL, NULL, 0, 0} +}; + +/* + *--------------------------------------------------------------------------- + * + * TurnOffHairs -- + * + * XOR's the existing line segments (representing the crosshairs), + * thereby erasing them. The internal state of the crosshairs is + * tracked. + * + * Results: + * None + * + * Side Effects: + * Crosshairs are erased. + * + *--------------------------------------------------------------------------- + */ +static void +TurnOffHairs(Tk_Window tkwin, Crosshairs *chPtr) +{ + if (Tk_IsMapped(tkwin) && (chPtr->visible)) { + XDrawSegments(Tk_Display(tkwin), Tk_WindowId(tkwin), chPtr->gc, + chPtr->segArr, 2); + chPtr->visible = FALSE; + } +} + +/* + *--------------------------------------------------------------------------- + * + * TurnOnHairs -- + * + * Draws (by XORing) new line segments, creating the effect of + * crosshairs. The internal state of the crosshairs is tracked. + * + * Results: + * None + * + * Side Effects: + * Crosshairs are displayed. + * + *--------------------------------------------------------------------------- + */ +static void +TurnOnHairs(Graph *graphPtr, Crosshairs *chPtr) +{ + if (Tk_IsMapped(graphPtr->tkwin) && (!chPtr->visible)) { + if (!PointInGraph(graphPtr, chPtr->hotSpot.x, chPtr->hotSpot.y)) { + return; /* Coordinates are off the graph */ + } + XDrawSegments(graphPtr->display, Tk_WindowId(graphPtr->tkwin), + chPtr->gc, chPtr->segArr, 2); + chPtr->visible = TRUE; + } +} + +/* + *--------------------------------------------------------------------------- + * + * ConfigureCrosshairs -- + * + * Configures attributes of the crosshairs such as line width, + * dashes, and position. The crosshairs are first turned off + * before any of the attributes changes. + * + * Results: + * None + * + * Side Effects: + * Crosshair GC is allocated. + * + *--------------------------------------------------------------------------- + */ +void +Blt_ConfigureCrosshairs(Graph *graphPtr) +{ + XGCValues gcValues; + unsigned long gcMask; + GC newGC; + unsigned long int pixel; + Crosshairs *chPtr = graphPtr->crosshairs; + + /* + * Turn off the crosshairs temporarily. This is in case the new + * configuration changes the size, style, or position of the lines. + */ + TurnOffHairs(graphPtr->tkwin, chPtr); + + gcValues.function = GXxor; + + if (graphPtr->plotBg == NULL) { + /* The graph's color option may not have been set yet */ + pixel = WhitePixelOfScreen(Tk_Screen(graphPtr->tkwin)); + } else { + pixel = Blt_BackgroundBorderColor(graphPtr->plotBg)->pixel; + } + gcValues.background = pixel; + gcValues.foreground = (pixel ^ chPtr->colorPtr->pixel); + + gcValues.line_width = LineWidth(chPtr->lineWidth); + gcMask = (GCForeground | GCBackground | GCFunction | GCLineWidth); + if (LineIsDashed(chPtr->dashes)) { + gcValues.line_style = LineOnOffDash; + gcMask |= GCLineStyle; + } + newGC = Blt_GetPrivateGC(graphPtr->tkwin, gcMask, &gcValues); + if (LineIsDashed(chPtr->dashes)) { + Blt_SetDashes(graphPtr->display, newGC, &chPtr->dashes); + } + if (chPtr->gc != NULL) { + Blt_FreePrivateGC(graphPtr->display, chPtr->gc); + } + chPtr->gc = newGC; + + /* + * Are the new coordinates on the graph? + */ + chPtr->segArr[0].x2 = chPtr->segArr[0].x1 = chPtr->hotSpot.x; + chPtr->segArr[0].y1 = graphPtr->bottom; + chPtr->segArr[0].y2 = graphPtr->top; + chPtr->segArr[1].y2 = chPtr->segArr[1].y1 = chPtr->hotSpot.y; + chPtr->segArr[1].x1 = graphPtr->left; + chPtr->segArr[1].x2 = graphPtr->right; + + if (!chPtr->hidden) { + TurnOnHairs(graphPtr, chPtr); + } +} + +void +Blt_EnableCrosshairs(Graph *graphPtr) +{ + if (!graphPtr->crosshairs->hidden) { + TurnOnHairs(graphPtr, graphPtr->crosshairs); + } +} + +void +Blt_DisableCrosshairs(Graph *graphPtr) +{ + if (!graphPtr->crosshairs->hidden) { + TurnOffHairs(graphPtr->tkwin, graphPtr->crosshairs); + } +} + +/* + *--------------------------------------------------------------------------- + * + * UpdateCrosshairs -- + * + * Update the length of the hairs (not the hot spot). + * + * Results: + * None. + * + *--------------------------------------------------------------------------- + */ +void +Blt_UpdateCrosshairs(Graph *graphPtr) +{ + Crosshairs *chPtr = graphPtr->crosshairs; + + chPtr->segArr[0].y1 = graphPtr->bottom; + chPtr->segArr[0].y2 = graphPtr->top; + chPtr->segArr[1].x1 = graphPtr->left; + chPtr->segArr[1].x2 = graphPtr->right; +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_DestroyCrosshairs -- + * + * Results: + * None + * + * Side Effects: + * Crosshair GC is allocated. + * + *--------------------------------------------------------------------------- + */ +void +Blt_DestroyCrosshairs(Graph *graphPtr) +{ + if (graphPtr->crosshairs != NULL) { + Crosshairs *chPtr = graphPtr->crosshairs; + + Blt_FreeOptions(configSpecs, (char *)chPtr, graphPtr->display, 0); + if (chPtr->gc != NULL) { + Blt_FreePrivateGC(graphPtr->display, chPtr->gc); + } + Blt_Free(chPtr); + } +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_CreateCrosshairs -- + * + * Creates and initializes a new crosshair structure. + * + * Results: + * Returns TCL_ERROR if the crosshair structure can't be created, + * otherwise TCL_OK. + * + * Side Effects: + * Crosshair GC is allocated. + * + *--------------------------------------------------------------------------- + */ +int +Blt_CreateCrosshairs(Graph *graphPtr) +{ + Crosshairs *chPtr; + + chPtr = Blt_AssertCalloc(1, sizeof(Crosshairs)); + chPtr->hidden = TRUE; + chPtr->hotSpot.x = chPtr->hotSpot.y = -1; + graphPtr->crosshairs = chPtr; + + if (Blt_ConfigureComponentFromObj(graphPtr->interp, graphPtr->tkwin, + "crosshairs", "Crosshairs", configSpecs, 0, (Tcl_Obj **)NULL, + (char *)chPtr, 0) != TCL_OK) { + return TCL_ERROR; + } + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * CgetOp -- + * + * Queries configuration attributes of the crosshairs such as + * line width, dashes, and position. + * + * Results: + * A standard TCL result. + * + *--------------------------------------------------------------------------- + */ +/* ARGSUSED */ +static int +CgetOp( + Graph *graphPtr, + Tcl_Interp *interp, + int objc, /* Not used. */ + Tcl_Obj *const *objv) +{ + Crosshairs *chPtr = graphPtr->crosshairs; + + return Blt_ConfigureValueFromObj(interp, graphPtr->tkwin, configSpecs, + (char *)chPtr, objv[3], 0); +} + +/* + *--------------------------------------------------------------------------- + * + * ConfigureOp -- + * + * Queries or resets configuration attributes of the crosshairs + * such as line width, dashes, and position. + * + * Results: + * A standard TCL result. + * + * Side Effects: + * Crosshairs are reset. + * + *--------------------------------------------------------------------------- + */ +static int +ConfigureOp( + Graph *graphPtr, + Tcl_Interp *interp, + int objc, + Tcl_Obj *const *objv) +{ + Crosshairs *chPtr = graphPtr->crosshairs; + + if (objc == 3) { + return Blt_ConfigureInfoFromObj(interp, graphPtr->tkwin, configSpecs, + (char *)chPtr, (Tcl_Obj *)NULL, 0); + } else if (objc == 4) { + return Blt_ConfigureInfoFromObj(interp, graphPtr->tkwin, configSpecs, + (char *)chPtr, objv[3], 0); + } + if (Blt_ConfigureWidgetFromObj(interp, graphPtr->tkwin, configSpecs, + objc - 3, objv + 3, (char *)chPtr, BLT_CONFIG_OBJV_ONLY) != TCL_OK) { + return TCL_ERROR; + } + Blt_ConfigureCrosshairs(graphPtr); + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * OnOp -- + * + * Maps the crosshairs. + * + * Results: + * A standard TCL result. + * + * Side Effects: + * Crosshairs are reset if necessary. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +OnOp( + Graph *graphPtr, + Tcl_Interp *interp, /* Not used. */ + int objc, /* Not used. */ + Tcl_Obj *const *objv) /* Not used. */ +{ + Crosshairs *chPtr = graphPtr->crosshairs; + + if (chPtr->hidden) { + TurnOnHairs(graphPtr, chPtr); + chPtr->hidden = FALSE; + } + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * OffOp -- + * + * Unmaps the crosshairs. + * + * Results: + * A standard TCL result. + * + * Side Effects: + * Crosshairs are reset if necessary. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +OffOp( + Graph *graphPtr, + Tcl_Interp *interp, /* Not used. */ + int objc, /* Not used. */ + Tcl_Obj *const *objv) /* Not used. */ +{ + Crosshairs *chPtr = graphPtr->crosshairs; + + if (!chPtr->hidden) { + TurnOffHairs(graphPtr->tkwin, chPtr); + chPtr->hidden = TRUE; + } + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * ToggleOp -- + * + * Toggles the state of the crosshairs. + * + * Results: + * A standard TCL result. + * + * Side Effects: + * Crosshairs are reset. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +ToggleOp( + Graph *graphPtr, + Tcl_Interp *interp, /* Not used. */ + int objc, /* Not used. */ + Tcl_Obj *const *objv) /* Not used. */ +{ + Crosshairs *chPtr = graphPtr->crosshairs; + + chPtr->hidden = (chPtr->hidden == 0); + if (chPtr->hidden) { + TurnOffHairs(graphPtr->tkwin, chPtr); + } else { + TurnOnHairs(graphPtr, chPtr); + } + return TCL_OK; +} + + +static Blt_OpSpec xhairOps[] = +{ + {"cget", 2, CgetOp, 4, 4, "option",}, + {"configure", 2, ConfigureOp, 3, 0, "?options...?",}, + {"off", 2, OffOp, 3, 3, "",}, + {"on", 2, OnOp, 3, 3, "",}, + {"toggle", 1, ToggleOp, 3, 3, "",}, +}; +static int nXhairOps = sizeof(xhairOps) / sizeof(Blt_OpSpec); + +/* + *--------------------------------------------------------------------------- + * + * Blt_CrosshairsOp -- + * + * User routine to configure crosshair simulation. Crosshairs + * are simulated by drawing line segments parallel to both axes + * using the XOR drawing function. The allows the lines to be + * erased (by drawing them again) without redrawing the entire + * graph. Care must be taken to erase crosshairs before redrawing + * the graph and redraw them after the graph is redraw. + * + * Results: + * The return value is a standard TCL result. + * + * Side Effects: + * Crosshairs may be drawn in the plotting area. + * + *--------------------------------------------------------------------------- + */ +int +Blt_CrosshairsOp( + Graph *graphPtr, + Tcl_Interp *interp, + int objc, + Tcl_Obj *const *objv) +{ + GraphCrosshairProc *proc; + + proc = Blt_GetOpFromObj(interp, nXhairOps, xhairOps, BLT_OP_ARG2, + objc, objv, 0); + if (proc == NULL) { + return TCL_ERROR; + } + return (*proc) (graphPtr, interp, objc, objv); +} diff --git a/src/bltGrLegd.C b/src/bltGrLegd.C new file mode 100644 index 0000000..9ed3169 --- /dev/null +++ b/src/bltGrLegd.C @@ -0,0 +1,3047 @@ + +/* + * bltGrLegd.c -- + * + * This module implements the legend for the BLT graph widget. + * + * Copyright 1993-2004 George A Howlett. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include "bltGraph.h" +#include "bltOp.h" +#include "bltGrElem.h" +#include "bltPicture.h" +#include <X11/Xutil.h> +#include <X11/Xatom.h> + +/* + * Selection related flags: + * + * SELECT_EXPORT Export the selection to X11. + * + * SELECT_PENDING A "selection" command idle task is pending. + * + * SELECT_CLEAR Clear selection flag of entry. + * + * SELECT_SET Set selection flag of entry. + * + * SELECT_TOGGLE Toggle selection flag of entry. + * + * SELECT_MASK Mask of selection set/clear/toggle flags. + * + * SELECT_SORTED Indicates if the entries in the selection + * should be sorted or displayed in the order + * they were selected. + * + */ + +#define SELECT_CLEAR (1<<16) +#define SELECT_EXPORT (1<<17) +#define SELECT_PENDING (1<<18) +#define SELECT_SET (1<<19) +#define SELECT_TOGGLE (SELECT_SET | SELECT_CLEAR) +#define SELECT_MASK (SELECT_SET | SELECT_CLEAR) +#define SELECT_SORTED (1<<20) + +#define RAISED (1<<21) + +#define SELECT_MODE_SINGLE (1<<0) +#define SELECT_MODE_MULTIPLE (1<<1) + +typedef int (GraphLegendProc)(Graph *graphPtr, Tcl_Interp *interp, int objc, + Tcl_Obj *const *objv); + +/* + * Legend -- + * + * Contains information specific to how the legend will be displayed. + */ +struct _Legend { + unsigned int flags; + ClassId classId; /* Type: Element or Marker. */ + + int nEntries; /* Number of element entries in + * table. */ + short int nColumns, nRows; /* Number of columns and rows in + * legend */ + short int width, height; /* Dimensions of the legend */ + short int entryWidth, entryHeight; + + int site; + short int xReq, yReq; /* User-requested site of legend, not + * the final actual position. Used in + * conjunction with the anchor below + * to determine location of the + * legend. */ + + Tk_Anchor anchor; /* Anchor of legend. Used to interpret + * the positioning point of the legend + * in the graph*/ + + int x, y; /* Computed origin of legend. */ + + Graph *graphPtr; + Tcl_Command cmdToken; /* Token for graph's widget command. */ + int reqColumns, reqRows; + + Blt_Pad ixPad, iyPad; /* # of pixels interior padding around + * legend entries */ + Blt_Pad xPad, yPad; /* # of pixels padding to exterior of + * legend */ + Tk_Window tkwin; /* If non-NULL, external window to draw + * legend. */ + TextStyle style; + + int maxSymSize; /* Size of largest symbol to be + * displayed. Used to calculate size + * of legend */ + XColor *fgColor; + Blt_Background activeBg; /* Active legend entry background + * color. */ + XColor *activeFgColor; + int activeRelief; /* 3-D effect on active entry. */ + int entryBW; /* Border width around each entry in + * legend. */ + Blt_Background normalBg; /* 3-D effect of legend. */ + int borderWidth; /* Width of legend 3-D border */ + int relief; /* 3-d effect of border around the + * legend: TK_RELIEF_RAISED etc. */ + + Blt_BindTable bindTable; + + int selRelief; + int selBW; + + XColor *selInFocusFgColor; /* Text color of a selected entry. */ + XColor *selOutFocusFgColor; + + Blt_Background selInFocusBg; + Blt_Background selOutFocusBg; + + XColor *focusColor; + Blt_Dashes focusDashes; /* Dash on-off value. */ + GC focusGC; /* Graphics context for the active + * label. */ + + const char *takeFocus; + int focus; /* Position of the focus entry. */ + + int cursorX, cursorY; /* Position of the insertion cursor in + * the textbox window. */ + short int cursorWidth; /* Size of the insertion cursor + * symbol. */ + short int cursorHeight; + Element *focusPtr; /* Element that currently has the + * focus. If NULL, no legend entry has + * the focus. */ + Element *selAnchorPtr; /* Fixed end of selection. Used to + * extend the selection while + * maintaining the other end of the + * selection. */ + Element *selMarkPtr; + Element *selFirstPtr; /* First element selected in current + * pick. */ + Element *selLastPtr; /* Last element selected in current + * pick. */ + int exportSelection; + int active; + int cursorOn; /* Indicates if the cursor is + * displayed. */ + int onTime, offTime; /* Time in milliseconds to wait before + * changing the cursor from off-to-on + * and on-to-off. Setting offTime to 0 + * makes the * cursor steady. */ + Tcl_TimerToken timerToken; /* Handle for a timer event called + * periodically to blink the cursor. */ + const char *selectCmd; /* TCL script that's invoked whenever + * the selection changes. */ + int selectMode; /* Mode of selection: single or + * multiple. */ + Blt_HashTable selectTable; /* Table of selected elements. Used to + * quickly determine whether an element + * is selected. */ + Blt_Chain selected; /* List of selected elements. */ + + const char *title; + unsigned int titleWidth, titleHeight; + TextStyle titleStyle; /* Legend title attributes */ +}; + +#define padLeft xPad.side1 +#define padRight xPad.side2 +#define padTop yPad.side1 +#define padBottom yPad.side2 +#define PADDING(x) ((x).side1 + (x).side2) +#define LABEL_PAD 2 + +#define DEF_LEGEND_ACTIVEBACKGROUND RGB_SKYBLUE4 +#define DEF_LEGEND_ACTIVEBORDERWIDTH "2" +#define DEF_LEGEND_ACTIVEFOREGROUND RGB_WHITE +#define DEF_LEGEND_ACTIVERELIEF "flat" +#define DEF_LEGEND_ANCHOR "n" +#define DEF_LEGEND_BACKGROUND (char *)NULL +#define DEF_LEGEND_BORDERWIDTH STD_BORDERWIDTH +#define DEF_LEGEND_COLUMNS "0" +#define DEF_LEGEND_EXPORTSELECTION "no" +#define DEF_LEGEND_FONT "{Sans Serif} 8" +#define DEF_LEGEND_FOREGROUND STD_NORMAL_FOREGROUND +#define DEF_LEGEND_HIDE "no" +#define DEF_LEGEND_IPADX "1" +#define DEF_LEGEND_IPADY "1" +#define DEF_LEGEND_PADX "1" +#define DEF_LEGEND_PADY "1" +#define DEF_LEGEND_POSITION "rightmargin" +#define DEF_LEGEND_RAISED "no" +#define DEF_LEGEND_RELIEF "flat" +#define DEF_LEGEND_ROWS "0" +#define DEF_LEGEND_SELECTBACKGROUND RGB_SKYBLUE4 +#define DEF_LEGEND_SELECT_BG_MONO STD_SELECT_BG_MONO +#define DEF_LEGEND_SELECTBORDERWIDTH "1" +#define DEF_LEGEND_SELECTMODE "multiple" +#define DEF_LEGEND_SELECT_FG_MONO STD_SELECT_FG_MONO +#define DEF_LEGEND_SELECTFOREGROUND RGB_WHITE /*STD_SELECT_FOREGROUND*/ +#define DEF_LEGEND_SELECTRELIEF "flat" +#define DEF_LEGEND_FOCUSDASHES "dot" +#define DEF_LEGEND_FOCUSEDIT "no" +#define DEF_LEGEND_FOCUSFOREGROUND STD_ACTIVE_FOREGROUND +#define DEF_LEGEND_FOCUS_FG_MONO STD_ACTIVE_FG_MONO +#define DEF_LEGEND_TAKEFOCUS "1" +#define DEF_LEGEND_TITLE (char *)NULL +#define DEF_LEGEND_TITLECOLOR STD_NORMAL_FOREGROUND +#define DEF_LEGEND_TITLEFONT "{Sans Serif} 8 bold" + +static Blt_OptionParseProc ObjToPosition; +static Blt_OptionPrintProc PositionToObj; +static Blt_CustomOption legendPositionOption = +{ + ObjToPosition, PositionToObj, NULL, (ClientData)0 +}; + +static Blt_OptionParseProc ObjToSelectmode; +static Blt_OptionPrintProc SelectmodeToObj; +static Blt_CustomOption selectmodeOption = { + ObjToSelectmode, SelectmodeToObj, NULL, NULL, +}; + +static Blt_ConfigSpec configSpecs[] = +{ + {BLT_CONFIG_BACKGROUND, "-activebackground", "activeBackground", + "ActiveBackground", DEF_LEGEND_ACTIVEBACKGROUND, + Blt_Offset(Legend, activeBg), 0}, + {BLT_CONFIG_PIXELS_NNEG, "-activeborderwidth", "activeBorderWidth", + "BorderWidth", DEF_LEGEND_BORDERWIDTH, + Blt_Offset(Legend, entryBW), BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_COLOR, "-activeforeground", "activeForeground", + "ActiveForeground", DEF_LEGEND_ACTIVEFOREGROUND, + Blt_Offset(Legend, activeFgColor), 0}, + {BLT_CONFIG_RELIEF, "-activerelief", "activeRelief", "Relief", + DEF_LEGEND_ACTIVERELIEF, Blt_Offset(Legend, activeRelief), + BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_ANCHOR, "-anchor", "anchor", "Anchor", DEF_LEGEND_ANCHOR, + Blt_Offset(Legend, anchor), BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_SYNONYM, "-bg", "background", (char *)NULL, (char *)NULL, 0, 0}, + {BLT_CONFIG_BACKGROUND, "-background", "background", "Background", + DEF_LEGEND_BACKGROUND, Blt_Offset(Legend, normalBg),BLT_CONFIG_NULL_OK}, + {BLT_CONFIG_PIXELS_NNEG, "-borderwidth", "borderWidth", "BorderWidth", + DEF_LEGEND_BORDERWIDTH, Blt_Offset(Legend, borderWidth), + BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_SYNONYM, "-bd", "borderWidth", (char *)NULL, (char *)NULL, 0,0}, + {BLT_CONFIG_INT_NNEG, "-columns", "columns", "columns", + DEF_LEGEND_COLUMNS, Blt_Offset(Legend, reqColumns), + BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_BITMASK, "-exportselection", "exportSelection", + "ExportSelection", DEF_LEGEND_EXPORTSELECTION, + Blt_Offset(Legend, flags), BLT_CONFIG_DONT_SET_DEFAULT, + (Blt_CustomOption *)SELECT_EXPORT}, + {BLT_CONFIG_DASHES, "-focusdashes", "focusDashes", "FocusDashes", + DEF_LEGEND_FOCUSDASHES, Blt_Offset(Legend, focusDashes), + BLT_CONFIG_NULL_OK}, + {BLT_CONFIG_COLOR, "-focusforeground", "focusForeground", "FocusForeground", + DEF_LEGEND_FOCUSFOREGROUND, Blt_Offset(Legend, focusColor), + BLT_CONFIG_COLOR_ONLY}, + {BLT_CONFIG_COLOR, "-focusforeground", "focusForeground", "FocusForeground", + DEF_LEGEND_FOCUS_FG_MONO, Blt_Offset(Legend, focusColor), + BLT_CONFIG_MONO_ONLY}, + {BLT_CONFIG_FONT, "-font", "font", "Font", DEF_LEGEND_FONT, + Blt_Offset(Legend, style.font), 0}, + {BLT_CONFIG_SYNONYM, "-fg", "foreground", (char *)NULL, (char *)NULL, 0, 0}, + {BLT_CONFIG_COLOR, "-foreground", "foreground", "Foreground", + DEF_LEGEND_FOREGROUND, Blt_Offset(Legend, fgColor), 0}, + {BLT_CONFIG_BITMASK, "-hide", "hide", "Hide", DEF_LEGEND_HIDE, + Blt_Offset(Legend, flags), BLT_CONFIG_DONT_SET_DEFAULT, + (Blt_CustomOption *)HIDE}, + {BLT_CONFIG_PAD, "-ipadx", "iPadX", "Pad", DEF_LEGEND_IPADX, + Blt_Offset(Legend, ixPad), BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_PAD, "-ipady", "iPadY", "Pad", DEF_LEGEND_IPADY, + Blt_Offset(Legend, iyPad), BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_BACKGROUND, "-nofocusselectbackground", + "noFocusSelectBackground", "NoFocusSelectBackground", + DEF_LEGEND_SELECTBACKGROUND, Blt_Offset(Legend, selOutFocusBg), 0}, + {BLT_CONFIG_COLOR, "-nofocusselectforeground", "noFocusSelectForeground", + "NoFocusSelectForeground", DEF_LEGEND_SELECTFOREGROUND, + Blt_Offset(Legend, selOutFocusFgColor), 0}, + {BLT_CONFIG_PAD, "-padx", "padX", "Pad", DEF_LEGEND_PADX, + Blt_Offset(Legend, xPad), BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_PAD, "-pady", "padY", "Pad", DEF_LEGEND_PADY, + Blt_Offset(Legend, yPad), BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_CUSTOM, "-position", "position", "Position", + DEF_LEGEND_POSITION, 0, BLT_CONFIG_DONT_SET_DEFAULT, + &legendPositionOption}, + {BLT_CONFIG_BITMASK, "-raised", "raised", "Raised", DEF_LEGEND_RAISED, + Blt_Offset(Legend, flags), BLT_CONFIG_DONT_SET_DEFAULT, + (Blt_CustomOption *)RAISED}, + {BLT_CONFIG_RELIEF, "-relief", "relief", "Relief", DEF_LEGEND_RELIEF, + Blt_Offset(Legend, relief), BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_INT_NNEG, "-rows", "rows", "rows", DEF_LEGEND_ROWS, + Blt_Offset(Legend, reqRows), + BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_BACKGROUND, "-selectbackground", "selectBackground", + "Background", DEF_LEGEND_SELECTBACKGROUND, + Blt_Offset(Legend, selInFocusBg), 0}, + {BLT_CONFIG_PIXELS_NNEG, "-selectborderwidth", "selectBorderWidth", + "BorderWidth", DEF_LEGEND_SELECTBORDERWIDTH, + Blt_Offset(Legend, selBW), BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_STRING, "-selectcommand", "selectCommand", "SelectCommand", + (char *)NULL, Blt_Offset(Legend, selectCmd), BLT_CONFIG_NULL_OK}, + {BLT_CONFIG_COLOR, "-selectforeground", "selectForeground", "Foreground", + DEF_LEGEND_SELECT_FG_MONO, Blt_Offset(Legend, selInFocusFgColor), + BLT_CONFIG_MONO_ONLY}, + {BLT_CONFIG_COLOR, "-selectforeground", "selectForeground", "Foreground", + DEF_LEGEND_SELECTFOREGROUND, Blt_Offset(Legend, selInFocusFgColor), + BLT_CONFIG_COLOR_ONLY}, + {BLT_CONFIG_CUSTOM, "-selectmode", "selectMode", "SelectMode", + DEF_LEGEND_SELECTMODE, Blt_Offset(Legend, selectMode), + BLT_CONFIG_DONT_SET_DEFAULT, &selectmodeOption}, + {BLT_CONFIG_RELIEF, "-selectrelief", "selectRelief", "Relief", + DEF_LEGEND_SELECTRELIEF, Blt_Offset(Legend, selRelief), + BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_STRING, "-takefocus", "takeFocus", "TakeFocus", + DEF_LEGEND_TAKEFOCUS, Blt_Offset(Legend, takeFocus), + BLT_CONFIG_NULL_OK}, + {BLT_CONFIG_STRING, "-title", "title", "Title", DEF_LEGEND_TITLE, + Blt_Offset(Legend, title), BLT_CONFIG_NULL_OK}, + {BLT_CONFIG_COLOR, "-titlecolor", "titleColor", "Foreground", + DEF_LEGEND_TITLECOLOR, Blt_Offset(Legend, titleStyle.color), 0}, + {BLT_CONFIG_FONT, "-titlefont", "titleFont", "Font", + DEF_LEGEND_TITLEFONT, Blt_Offset(Legend, titleStyle.font), 0}, + {BLT_CONFIG_END, NULL, NULL, NULL, NULL, 0, 0} +}; + +static Tcl_IdleProc DisplayLegend; +static Blt_BindPickProc PickEntryProc; +static Tk_EventProc LegendEventProc; +static Tcl_TimerProc BlinkCursorProc; +static Tk_LostSelProc LostSelectionProc; +static Tk_SelectionProc SelectionProc; + +BLT_EXTERN Tcl_ObjCmdProc Blt_GraphInstCmdProc; + +/* + *--------------------------------------------------------------------------- + * + * Blt_Legend_EventuallyRedraw -- + * + * Tells the Tk dispatcher to call the graph display routine at the next + * idle point. This request is made only if the window is displayed and + * no other redraw request is pending. + * + * Results: None. + * + * Side effects: + * The window is eventually redisplayed. + * + *--------------------------------------------------------------------------- + */ +void +Blt_Legend_EventuallyRedraw(Graph *graphPtr) +{ + Legend *legendPtr = graphPtr->legend; + + if ((legendPtr->tkwin != NULL) && !(legendPtr->flags & REDRAW_PENDING)) { + Tcl_DoWhenIdle(DisplayLegend, legendPtr); + legendPtr->flags |= REDRAW_PENDING; + } +} + +/* + *--------------------------------------------------------------------------- + * + * SelectCmdProc -- + * + * Invoked at the next idle point whenever the current selection changes. + * Executes some application-specific code in the -selectcommand option. + * This provides a way for applications to handle selection changes. + * + * Results: + * None. + * + * Side effects: + * TCL code gets executed for some application-specific task. + * + *--------------------------------------------------------------------------- + */ +static void +SelectCmdProc(ClientData clientData) +{ + Legend *legendPtr = clientData; + + Tcl_Preserve(legendPtr); + legendPtr->flags &= ~SELECT_PENDING; + if (legendPtr->selectCmd != NULL) { + Tcl_Interp *interp; + + interp = legendPtr->graphPtr->interp; + if (Tcl_GlobalEval(interp, legendPtr->selectCmd) != TCL_OK) { + Tcl_BackgroundError(interp); + } + } + Tcl_Release(legendPtr); +} + +/* + *--------------------------------------------------------------------------- + * + * EventuallyInvokeSelectCmd -- + * + * Queues a request to execute the -selectcommand code associated with + * the widget at the next idle point. Invoked whenever the selection + * changes. + * + * Results: + * None. + * + * Side effects: + * TCL code gets executed for some application-specific task. + * + *--------------------------------------------------------------------------- + */ +static void +EventuallyInvokeSelectCmd(Legend *legendPtr) +{ + if ((legendPtr->flags & SELECT_PENDING) == 0) { + legendPtr->flags |= SELECT_PENDING; + Tcl_DoWhenIdle(SelectCmdProc, legendPtr); + } +} + +static void +ClearSelection(Legend *legendPtr) +{ + Blt_DeleteHashTable(&legendPtr->selectTable); + Blt_InitHashTable(&legendPtr->selectTable, BLT_ONE_WORD_KEYS); + Blt_Chain_Reset(legendPtr->selected); + Blt_Legend_EventuallyRedraw(legendPtr->graphPtr); + if (legendPtr->selectCmd != NULL) { + EventuallyInvokeSelectCmd(legendPtr); + } +} + + +/* + *--------------------------------------------------------------------------- + * + * LostSelectionProc -- + * + * This procedure is called back by Tk when the selection is grabbed away + * from a Text widget. + * + * Results: + * None. + * + * Side effects: + * The existing selection is unhighlighted, and the window is marked as + * not containing a selection. + * + *--------------------------------------------------------------------------- + */ +static void +LostSelectionProc(ClientData clientData) +{ + Legend *legendPtr = clientData; + + if (legendPtr->flags & SELECT_EXPORT) { + ClearSelection(legendPtr); + } +} + +/* + *--------------------------------------------------------------------------- + * + * LegendEventProc -- + * + * This procedure is invoked by the Tk dispatcher for various events on + * graphs. + * + * Results: + * None. + * + * Side effects: + * When the window gets deleted, internal structures get cleaned up. + * When it gets exposed, the graph is eventually redisplayed. + * + *--------------------------------------------------------------------------- + */ +static void +LegendEventProc(ClientData clientData, XEvent *eventPtr) +{ + Graph *graphPtr = clientData; + Legend *legendPtr; + + legendPtr = graphPtr->legend; + if (eventPtr->type == Expose) { + if (eventPtr->xexpose.count == 0) { + Blt_Legend_EventuallyRedraw(graphPtr); + } + } else if ((eventPtr->type == FocusIn) || (eventPtr->type == FocusOut)) { + if (eventPtr->xfocus.detail == NotifyInferior) { + return; + } + if (eventPtr->type == FocusIn) { + legendPtr->flags |= FOCUS; + } else { + legendPtr->flags &= ~FOCUS; + } + Tcl_DeleteTimerHandler(legendPtr->timerToken); + if ((legendPtr->active) && (legendPtr->flags & FOCUS)) { + legendPtr->cursorOn = TRUE; + if (legendPtr->offTime != 0) { + legendPtr->timerToken = Tcl_CreateTimerHandler( + legendPtr->onTime, BlinkCursorProc, graphPtr); + } + } else { + legendPtr->cursorOn = FALSE; + legendPtr->timerToken = (Tcl_TimerToken)NULL; + } + Blt_Legend_EventuallyRedraw(graphPtr); + } else if (eventPtr->type == DestroyNotify) { + Graph *graphPtr = legendPtr->graphPtr; + + if (legendPtr->site == LEGEND_WINDOW) { + Blt_DeleteWindowInstanceData(legendPtr->tkwin); + if (legendPtr->cmdToken != NULL) { + Tcl_DeleteCommandFromToken(graphPtr->interp, + legendPtr->cmdToken); + legendPtr->cmdToken = NULL; + } + legendPtr->tkwin = graphPtr->tkwin; + } + if (legendPtr->flags & REDRAW_PENDING) { + Tcl_CancelIdleCall(DisplayLegend, legendPtr); + legendPtr->flags &= ~REDRAW_PENDING; + } + legendPtr->site = LEGEND_RIGHT; + legendPtr->flags |= HIDE; + graphPtr->flags |= (MAP_WORLD | REDRAW_WORLD); + Blt_MoveBindingTable(legendPtr->bindTable, graphPtr->tkwin); + Blt_EventuallyRedrawGraph(graphPtr); + } else if (eventPtr->type == ConfigureNotify) { + Blt_Legend_EventuallyRedraw(graphPtr); + } +} + +static int +CreateLegendWindow(Tcl_Interp *interp, Legend *legendPtr, const char *pathName) +{ + Graph *graphPtr; + Tk_Window tkwin; + + graphPtr = legendPtr->graphPtr; + tkwin = Tk_CreateWindowFromPath(interp, graphPtr->tkwin, pathName, NULL); + if (tkwin == NULL) { + return TCL_ERROR; + } + Blt_SetWindowInstanceData(tkwin, legendPtr); + Tk_CreateEventHandler(tkwin, ExposureMask | StructureNotifyMask, + LegendEventProc, graphPtr); + /* Move the legend's binding table to the new window. */ + Blt_MoveBindingTable(legendPtr->bindTable, tkwin); + if (legendPtr->tkwin != graphPtr->tkwin) { + Tk_DestroyWindow(legendPtr->tkwin); + } + /* Create a command by the same name as the legend window so that Legend + * bindings can use %W interchangably. */ + legendPtr->cmdToken = Tcl_CreateObjCommand(interp, pathName, + Blt_GraphInstCmdProc, graphPtr, NULL); + legendPtr->tkwin = tkwin; + legendPtr->site = LEGEND_WINDOW; + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * ObjToPosition -- + * + * Convert the string representation of a legend XY position into window + * coordinates. The form of the string must be "@x,y" or none. + * + * Results: + * The return value is a standard TCL result. The symbol type is written + * into the widget record. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +ObjToPosition( + ClientData clientData, /* Not used. */ + Tcl_Interp *interp, /* Interpreter to send results back + * to */ + Tk_Window tkwin, /* Not used. */ + Tcl_Obj *objPtr, /* New legend position string */ + char *widgRec, /* Widget record */ + int offset, /* Not used. */ + int flags) /* Not used. */ +{ + Graph *graphPtr; + Legend *legendPtr = (Legend *)widgRec; + char c; + int length; + const char *string; + + graphPtr = legendPtr->graphPtr; + string = Tcl_GetStringFromObj(objPtr, &length); + c = string[0]; + if (c == '\0') { + legendPtr->site = LEGEND_RIGHT; + } else if ((c == 'l') && (strncmp(string, "leftmargin", length) == 0)) { + legendPtr->site = LEGEND_LEFT; + } else if ((c == 'r') && (strncmp(string, "rightmargin", length) == 0)) { + legendPtr->site = LEGEND_RIGHT; + } else if ((c == 't') && (strncmp(string, "topmargin", length) == 0)) { + legendPtr->site = LEGEND_TOP; + } else if ((c == 'b') && (strncmp(string, "bottommargin", length) == 0)) { + legendPtr->site = LEGEND_BOTTOM; + } else if ((c == 'p') && (strncmp(string, "plotarea", length) == 0)) { + legendPtr->site = LEGEND_PLOT; + } else if (c == '@') { + char *comma; + long x, y; + int result; + + comma = strchr(string + 1, ','); + if (comma == NULL) { + Tcl_AppendResult(interp, "bad screen position \"", string, + "\": should be @x,y", (char *)NULL); + return TCL_ERROR; + } + x = y = 0; + *comma = '\0'; + result = ((Tcl_ExprLong(interp, string + 1, &x) == TCL_OK) && + (Tcl_ExprLong(interp, comma + 1, &y) == TCL_OK)); + *comma = ','; + if (!result) { + return TCL_ERROR; + } + legendPtr->xReq = x; + legendPtr->yReq = y; + legendPtr->site = LEGEND_XY; + } else if (c == '.') { + if (CreateLegendWindow(interp, legendPtr, string) != TCL_OK) { + return TCL_ERROR; + } + Blt_Legend_EventuallyRedraw(graphPtr); + } else { + Tcl_AppendResult(interp, "bad position \"", string, "\": should be \ +\"leftmargin\", \"rightmargin\", \"topmargin\", \"bottommargin\", \ +\"plotarea\", windowName or @x,y", (char *)NULL); + return TCL_ERROR; + } + graphPtr->flags |= RESET_WORLD; + Blt_EventuallyRedrawGraph(graphPtr); + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * PositionToObj -- + * + * Convert the window coordinates into a string. + * + * Results: + * The string representing the coordinate position is returned. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static Tcl_Obj * +PositionToObj( + ClientData clientData, /* Not used. */ + Tcl_Interp *interp, /* Not used. */ + Tk_Window tkwin, /* Not used. */ + char *widgRec, /* Widget record */ + int offset, /* Not used. */ + int flags) /* Not used. */ +{ + Legend *legendPtr = (Legend *)widgRec; + Tcl_Obj *objPtr; + + switch (legendPtr->site) { + case LEGEND_LEFT: + objPtr = Tcl_NewStringObj("leftmargin", -1); + break; + + case LEGEND_RIGHT: + objPtr = Tcl_NewStringObj("rightmargin", -1); + break; + + case LEGEND_TOP: + objPtr = Tcl_NewStringObj("topmargin", -1); + break; + + case LEGEND_BOTTOM: + objPtr = Tcl_NewStringObj("bottommargin", -1); + break; + + case LEGEND_PLOT: + objPtr = Tcl_NewStringObj("plotarea", -1); + break; + + case LEGEND_WINDOW: + objPtr = Tcl_NewStringObj(Tk_PathName(legendPtr->tkwin), -1); + break; + + case LEGEND_XY: + { + char string[200]; + + sprintf_s(string, 200, "@%d,%d", legendPtr->xReq, legendPtr->yReq); + objPtr = Tcl_NewStringObj(string, -1); + } + default: + objPtr = Tcl_NewStringObj("unknown legend position", -1); + } + return objPtr; +} + +/* + *--------------------------------------------------------------------------- + * + * ObjToSelectmode -- + * + * Convert the string reprsenting a select mode, to its numeric form. + * + * Results: + * If the string is successfully converted, TCL_OK is returned. + * Otherwise, TCL_ERROR is returned and an error message is left + * in interpreter's result field. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +ObjToSelectmode( + ClientData clientData, /* Not used. */ + Tcl_Interp *interp, /* Interpreter to send results back to */ + Tk_Window tkwin, /* Not used. */ + Tcl_Obj *objPtr, /* Tcl_Obj representing the new value. */ + char *widgRec, + int offset, /* Offset to field in structure */ + int flags) +{ + char *string; + char c; + int *modePtr = (int *)(widgRec + offset); + + string = Tcl_GetString(objPtr); + c = string[0]; + if ((c == 's') && (strcmp(string, "single") == 0)) { + *modePtr = SELECT_MODE_SINGLE; + } else if ((c == 'm') && (strcmp(string, "multiple") == 0)) { + *modePtr = SELECT_MODE_MULTIPLE; + } else if ((c == 'a') && (strcmp(string, "active") == 0)) { + *modePtr = SELECT_MODE_SINGLE; + } else { + Tcl_AppendResult(interp, "bad select mode \"", string, + "\": should be \"single\" or \"multiple\"", (char *)NULL); + return TCL_ERROR; + } + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * SelectmodeToObj -- + * + * Results: + * The string representation of the select mode is returned. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static Tcl_Obj * +SelectmodeToObj( + ClientData clientData, /* Not used. */ + Tcl_Interp *interp, + Tk_Window tkwin, /* Not used. */ + char *widgRec, + int offset, /* Offset to field in structure */ + int flags) +{ + int mode = *(int *)(widgRec + offset); + + switch (mode) { + case SELECT_MODE_SINGLE: + return Tcl_NewStringObj("single", -1); + case SELECT_MODE_MULTIPLE: + return Tcl_NewStringObj("multiple", -1); + default: + return Tcl_NewStringObj("unknown scroll mode", -1); + } +} + + +static void +SetLegendOrigin(Legend *legendPtr) +{ + Graph *graphPtr; + int x, y, w, h; + + graphPtr = legendPtr->graphPtr; + x = y = w = h = 0; /* Suppress compiler warning. */ + switch (legendPtr->site) { + case LEGEND_RIGHT: + w = graphPtr->rightMargin.width - graphPtr->rightMargin.axesOffset; + h = graphPtr->bottom - graphPtr->top; + x = graphPtr->right + graphPtr->rightMargin.axesOffset; + y = graphPtr->top; + break; + + case LEGEND_LEFT: + w = graphPtr->leftMargin.width - graphPtr->leftMargin.axesOffset; + h = graphPtr->bottom - graphPtr->top; + x = graphPtr->inset; + y = graphPtr->top; + break; + + case LEGEND_TOP: + w = graphPtr->right - graphPtr->left; + h = graphPtr->topMargin.height - graphPtr->topMargin.axesOffset; + if (graphPtr->title != NULL) { + h -= graphPtr->titleHeight; + } + x = graphPtr->left; + y = graphPtr->inset; + if (graphPtr->title != NULL) { + y += graphPtr->titleHeight; + } + break; + + case LEGEND_BOTTOM: + w = graphPtr->right - graphPtr->left; + h = graphPtr->bottomMargin.height - graphPtr->bottomMargin.axesOffset; + x = graphPtr->left; + y = graphPtr->bottom + graphPtr->bottomMargin.axesOffset; + break; + + case LEGEND_PLOT: + w = graphPtr->right - graphPtr->left; + h = graphPtr->bottom - graphPtr->top; + x = graphPtr->left; + y = graphPtr->top; + break; + + case LEGEND_XY: + w = legendPtr->width; + h = legendPtr->height; + x = legendPtr->xReq; + y = legendPtr->yReq; + if (x < 0) { + x += graphPtr->width; + } + if (y < 0) { + y += graphPtr->height; + } + break; + + case LEGEND_WINDOW: + legendPtr->anchor = TK_ANCHOR_NW; + legendPtr->x = legendPtr->y = 0; + return; + } + + switch (legendPtr->anchor) { + case TK_ANCHOR_NW: /* Upper left corner */ + break; + case TK_ANCHOR_W: /* Left center */ + if (h > legendPtr->height) { + y += (h - legendPtr->height) / 2; + } + break; + case TK_ANCHOR_SW: /* Lower left corner */ + if (h > legendPtr->height) { + y += (h - legendPtr->height); + } + break; + case TK_ANCHOR_N: /* Top center */ + if (w > legendPtr->width) { + x += (w - legendPtr->width) / 2; + } + break; + case TK_ANCHOR_CENTER: /* Center */ + if (h > legendPtr->height) { + y += (h - legendPtr->height) / 2; + } + if (w > legendPtr->width) { + x += (w - legendPtr->width) / 2; + } + break; + case TK_ANCHOR_S: /* Bottom center */ + if (w > legendPtr->width) { + x += (w - legendPtr->width) / 2; + } + if (h > legendPtr->height) { + y += (h - legendPtr->height); + } + break; + case TK_ANCHOR_NE: /* Upper right corner */ + if (w > legendPtr->width) { + x += w - legendPtr->width; + } + break; + case TK_ANCHOR_E: /* Right center */ + if (w > legendPtr->width) { + x += w - legendPtr->width; + } + if (h > legendPtr->height) { + y += (h - legendPtr->height) / 2; + } + break; + case TK_ANCHOR_SE: /* Lower right corner */ + if (w > legendPtr->width) { + x += w - legendPtr->width; + } + if (h > legendPtr->height) { + y += (h - legendPtr->height); + } + break; + } + legendPtr->x = x + legendPtr->padLeft; + legendPtr->y = y + legendPtr->padTop; +} + +static int +EntryIsSelected(Legend *legendPtr, Element *elemPtr) +{ + Blt_HashEntry *hPtr; + + hPtr = Blt_FindHashEntry(&legendPtr->selectTable, (char *)elemPtr); + return (hPtr != NULL); +} + +static void +SelectElement(Legend *legendPtr, Element *elemPtr) +{ + int isNew; + Blt_HashEntry *hPtr; + + hPtr = Blt_CreateHashEntry(&legendPtr->selectTable, (char *)elemPtr,&isNew); + if (isNew) { + Blt_ChainLink link; + + link = Blt_Chain_Append(legendPtr->selected, elemPtr); + Blt_SetHashValue(hPtr, link); + } +} + +static void +DeselectElement(Legend *legendPtr, Element *elemPtr) +{ + Blt_HashEntry *hPtr; + + hPtr = Blt_FindHashEntry(&legendPtr->selectTable, (char *)elemPtr); + if (hPtr != NULL) { + Blt_ChainLink link; + + link = Blt_GetHashValue(hPtr); + Blt_Chain_DeleteLink(legendPtr->selected, link); + Blt_DeleteHashEntry(&legendPtr->selectTable, hPtr); + } +} + +static void +SelectEntry(Legend *legendPtr, Element *elemPtr) +{ + Blt_HashEntry *hPtr; + + switch (legendPtr->flags & SELECT_MASK) { + case SELECT_CLEAR: + DeselectElement(legendPtr, elemPtr); + break; + + case SELECT_SET: + SelectElement(legendPtr, elemPtr); + break; + + case SELECT_TOGGLE: + hPtr = Blt_FindHashEntry(&legendPtr->selectTable, (char *)elemPtr); + if (hPtr != NULL) { + DeselectElement(legendPtr, elemPtr); + } else { + SelectElement(legendPtr, elemPtr); + } + break; + } +} + +#ifdef notdef +static Element * +PointerToElement(Legend *legendPtr, int x, int y) +{ + Graph *graphPtr = legendPtr->graphPtr; + int w, h; + int n; + + w = legendPtr->width; + h = legendPtr->height; + + x -= legendPtr->x + legendPtr->borderWidth; + y -= legendPtr->y + legendPtr->borderWidth; + w -= 2 * legendPtr->borderWidth + PADDING(legendPtr->xPad); + h -= 2 * legendPtr->borderWidth + PADDING(legendPtr->yPad); + + if ((x < 0) || (x >= w) || (y < 0) || (y >= h)) { + return NULL; + } + + /* It's in the bounding box, so compute the index. */ + { + int row, column; + + row = y / legendPtr->entryHeight; + column = x / legendPtr->entryWidth; + n = (column * legendPtr->nRows) + row; + } + if (n < legendPtr->nEntries) { + Blt_ChainLink link; + int count; + + count = 0; + for (link = Blt_Chain_FirstLink(graphPtr->elements.displayList); + link != NULL; link = Blt_Chain_NextLink(link)) { + Element *elemPtr; + + elemPtr = Blt_Chain_GetValue(link); + if (elemPtr->label == NULL) { + continue; + } + if (count > n) { + return NULL; + } else if (count == n) { + return elemPtr; + } + count++; + } + } + return NULL; +} +#endif + +/*ARGSUSED*/ +static ClientData +PickEntryProc(ClientData clientData, int x, int y, ClientData *contextPtr) +{ + Graph *graphPtr = clientData; + Legend *legendPtr; + int w, h; + + legendPtr = graphPtr->legend; + w = legendPtr->width; + h = legendPtr->height; + + if (legendPtr->titleHeight > 0) { + y -= legendPtr->titleHeight + legendPtr->yPad.side1; + } + x -= legendPtr->x + legendPtr->borderWidth; + y -= legendPtr->y + legendPtr->borderWidth; + w -= 2 * legendPtr->borderWidth + PADDING(legendPtr->xPad); + h -= 2 * legendPtr->borderWidth + PADDING(legendPtr->yPad); + + if ((x >= 0) && (x < w) && (y >= 0) && (y < h)) { + int row, column; + int n; + + /* + * It's in the bounding box, so compute the index. + */ + row = y / legendPtr->entryHeight; + column = x / legendPtr->entryWidth; + n = (column * legendPtr->nRows) + row; + if (n < legendPtr->nEntries) { + Blt_ChainLink link; + int count; + + /* Legend entries are stored in bottom-to-top. */ + count = 0; + for (link = Blt_Chain_FirstLink(graphPtr->elements.displayList); + link != NULL; link = Blt_Chain_NextLink(link)) { + Element *elemPtr; + + elemPtr = Blt_Chain_GetValue(link); + if (elemPtr->label != NULL) { + if (count == n) { + return elemPtr; + } + count++; + } + } + if (link != NULL) { + return Blt_Chain_GetValue(link); + } + } + } + return NULL; +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_MapLegend -- + * + * Calculates the dimensions (width and height) needed for the legend. + * Also determines the number of rows and columns necessary to list all + * the valid element labels. + * + * Results: + * None. + * + * Side effects: + * The following fields of the legend are calculated and set. + * + * nEntries - number of valid labels of elements in the + * display list. + * nRows - number of rows of entries + * nColumns - number of columns of entries + * entryHeight - height of each entry + * entryWidth - width of each entry + * height - width of legend (includes borders and padding) + * width - height of legend (includes borders and padding) + * + *--------------------------------------------------------------------------- + */ +void +Blt_MapLegend( + Graph *graphPtr, + int plotWidth, /* Maximum width available in window + * to draw the legend. Will calculate + * # of columns from this. */ + int plotHeight) /* Maximum height available in window + * to draw the legend. Will calculate + * # of rows from this. */ +{ + Legend *legendPtr = graphPtr->legend; + Blt_ChainLink link; + int nRows, nColumns, nEntries; + int lw, lh; + int maxWidth, maxHeight; + int symbolWidth; + Blt_FontMetrics fontMetrics; + + /* Initialize legend values to default (no legend displayed) */ + legendPtr->entryWidth = legendPtr->entryHeight = 0; + legendPtr->nRows = legendPtr->nColumns = legendPtr->nEntries = 0; + legendPtr->height = legendPtr->width = 0; + + if (legendPtr->site == LEGEND_WINDOW) { + if (Tk_Width(legendPtr->tkwin) > 1) { + plotWidth = Tk_Width(legendPtr->tkwin); + } + if (Tk_Height(legendPtr->tkwin) > 1) { + plotHeight = Tk_Height(legendPtr->tkwin); + } + } + Blt_Ts_GetExtents(&legendPtr->titleStyle, legendPtr->title, + &legendPtr->titleWidth, &legendPtr->titleHeight); + /* + * Count the number of legend entries and determine the widest and tallest + * label. The number of entries would normally be the number of elements, + * but elements can have no legend entry (-label ""). + */ + nEntries = 0; + maxWidth = maxHeight = 0; + for (link = Blt_Chain_FirstLink(graphPtr->elements.displayList); + link != NULL; link = Blt_Chain_NextLink(link)) { + unsigned int w, h; + Element *elemPtr; + + elemPtr = Blt_Chain_GetValue(link); + if (elemPtr->label == NULL) { + continue; /* Element has no legend entry. */ + } + Blt_Ts_GetExtents(&legendPtr->style, elemPtr->label, &w, &h); + if (maxWidth < w) { + maxWidth = w; + } + if (maxHeight < h) { + maxHeight = h; + } + nEntries++; + } + if (nEntries == 0) { + return; /* No visible legend entries. */ + } + + + Blt_GetFontMetrics(legendPtr->style.font, &fontMetrics); + symbolWidth = 2 * fontMetrics.ascent; + + maxWidth += 2 * legendPtr->entryBW + PADDING(legendPtr->ixPad) + + + symbolWidth + 3 * LABEL_PAD; + + maxHeight += 2 * legendPtr->entryBW + PADDING(legendPtr->iyPad); + + maxWidth |= 0x01; + maxHeight |= 0x01; + + lw = plotWidth - 2 * legendPtr->borderWidth - PADDING(legendPtr->xPad); + lh = plotHeight - 2 * legendPtr->borderWidth - PADDING(legendPtr->yPad); + + /* + * The number of rows and columns is computed as one of the following: + * + * both options set User defined. + * -rows Compute columns from rows. + * -columns Compute rows from columns. + * neither set Compute rows and columns from + * size of plot. + */ + if (legendPtr->reqRows > 0) { + nRows = MIN(legendPtr->reqRows, nEntries); + if (legendPtr->reqColumns > 0) { + nColumns = MIN(legendPtr->reqColumns, nEntries); + } else { + nColumns = ((nEntries - 1) / nRows) + 1; /* Only -rows. */ + } + } else if (legendPtr->reqColumns > 0) { /* Only -columns. */ + nColumns = MIN(legendPtr->reqColumns, nEntries); + nRows = ((nEntries - 1) / nColumns) + 1; + } else { + /* Compute # of rows and columns from the legend size. */ + nRows = lh / maxHeight; + nColumns = lw / maxWidth; + if (nRows < 1) { + nRows = nEntries; + } + if (nColumns < 1) { + nColumns = nEntries; + } + if (nRows > nEntries) { + nRows = nEntries; + } + switch (legendPtr->site) { + case LEGEND_TOP: + case LEGEND_BOTTOM: + nRows = ((nEntries - 1) / nColumns) + 1; + break; + case LEGEND_LEFT: + case LEGEND_RIGHT: + default: + nColumns = ((nEntries - 1) / nRows) + 1; + break; + } + } + if (nColumns < 1) { + nColumns = 1; + } + if (nRows < 1) { + nRows = 1; + } + + lh = (nRows * maxHeight); + if (legendPtr->titleHeight > 0) { + lh += legendPtr->titleHeight + legendPtr->yPad.side1; + } + lw = nColumns * maxWidth; + if (lw < legendPtr->titleWidth) { + lw = legendPtr->titleWidth; + } + legendPtr->width = lw + 2 * legendPtr->borderWidth + + PADDING(legendPtr->xPad); + legendPtr->height = lh + 2 * legendPtr->borderWidth + + PADDING(legendPtr->yPad); + legendPtr->nRows = nRows; + legendPtr->nColumns = nColumns; + legendPtr->nEntries = nEntries; + legendPtr->entryHeight = maxHeight; + legendPtr->entryWidth = maxWidth; + + { + int row, col, count; + + row = col = count = 0; + for (link = Blt_Chain_FirstLink(graphPtr->elements.displayList); + link != NULL; link = Blt_Chain_NextLink(link)) { + Element *elemPtr; + + elemPtr = Blt_Chain_GetValue(link); + count++; + elemPtr->row = row; + elemPtr->col = col; + row++; + if ((count % nRows) == 0) { + col++; + row = 0; + } + } + } + if ((legendPtr->site == LEGEND_WINDOW) && + ((Tk_ReqWidth(legendPtr->tkwin) != legendPtr->width) || + (Tk_ReqHeight(legendPtr->tkwin) != legendPtr->height))) { + Tk_GeometryRequest(legendPtr->tkwin,legendPtr->width,legendPtr->height); + } +} + +void +Blt_DrawLegend(Graph *graphPtr, Drawable drawable) +{ + Blt_Background bg; + Blt_ChainLink link; + Blt_FontMetrics fontMetrics; + Legend *legendPtr = graphPtr->legend; + Pixmap pixmap; + Tk_Window tkwin; + int count; + int symbolSize, xMid, yMid; + int x, y, w, h; + int xLabel, yStart, xSymbol, ySymbol; + + if ((legendPtr->flags & HIDE) || (legendPtr->nEntries == 0)) { + return; + } + + SetLegendOrigin(legendPtr); + graphPtr = legendPtr->graphPtr; + tkwin = legendPtr->tkwin; + if (legendPtr->site == LEGEND_WINDOW) { + w = Tk_Width(tkwin); + h = Tk_Height(tkwin); + } else { + w = legendPtr->width; + h = legendPtr->height; + } + + pixmap = Tk_GetPixmap(graphPtr->display, Tk_WindowId(tkwin), w, h, + Tk_Depth(tkwin)); + + if (legendPtr->normalBg != NULL) { + Blt_FillBackgroundRectangle(tkwin, pixmap, legendPtr->normalBg, 0, 0, + w, h, 0, TK_RELIEF_FLAT); + } else if (legendPtr->site & LEGEND_PLOTAREA_MASK) { + /* + * Legend background is transparent and is positioned over the the + * plot area. Either copy the part of the background from the backing + * store pixmap or (if no backing store exists) just fill it with the + * background color of the plot. + */ + if (graphPtr->cache != None) { + XCopyArea(graphPtr->display, graphPtr->cache, pixmap, + graphPtr->drawGC, legendPtr->x, legendPtr->y, w, h, 0, 0); + } else { + Blt_FillBackgroundRectangle(tkwin, pixmap, graphPtr->plotBg, 0, 0, + w, h, TK_RELIEF_FLAT, 0); + } + } else { + int xOrigin, yOrigin; + /* + * The legend is located in one of the margins or the external window. + */ + Blt_GetBackgroundOrigin(graphPtr->normalBg, &xOrigin, &yOrigin); + Blt_SetBackgroundOrigin(graphPtr->tkwin, graphPtr->normalBg, + xOrigin - legendPtr->x,yOrigin - legendPtr->y); + Blt_FillBackgroundRectangle(tkwin, pixmap, graphPtr->normalBg, 0, 0, + w, h, 0, TK_RELIEF_FLAT); + Blt_SetBackgroundOrigin(tkwin, graphPtr->normalBg, xOrigin, yOrigin); + } + Blt_GetFontMetrics(legendPtr->style.font, &fontMetrics); + + symbolSize = fontMetrics.ascent; + xMid = symbolSize + 1 + legendPtr->entryBW; + yMid = (symbolSize / 2) + 1 + legendPtr->entryBW; + xLabel = 2 * symbolSize + legendPtr->entryBW + + legendPtr->ixPad.side1 + 2 * LABEL_PAD; + ySymbol = yMid + legendPtr->iyPad.side1; + xSymbol = xMid + LABEL_PAD; + + x = legendPtr->padLeft + legendPtr->borderWidth; + y = legendPtr->padTop + legendPtr->borderWidth; + Blt_DrawText(tkwin, pixmap, legendPtr->title, &legendPtr->titleStyle, x, y); + if (legendPtr->titleHeight > 0) { + y += legendPtr->titleHeight + legendPtr->yPad.side1; + } + count = 0; + yStart = y; + for (link = Blt_Chain_FirstLink(graphPtr->elements.displayList); + link != NULL; link = Blt_Chain_NextLink(link)) { + Element *elemPtr; + int isSelected; + + elemPtr = Blt_Chain_GetValue(link); + if (elemPtr->label == NULL) { + continue; /* Skip this entry */ + } + isSelected = EntryIsSelected(legendPtr, elemPtr); + if (elemPtr->flags & LABEL_ACTIVE) { + int xOrigin, yOrigin; + + Blt_GetBackgroundOrigin(legendPtr->activeBg, &xOrigin, &yOrigin); + Blt_SetBackgroundOrigin(tkwin, legendPtr->activeBg, + xOrigin - legendPtr->x, yOrigin - legendPtr->y); + Blt_Ts_SetForeground(legendPtr->style, legendPtr->activeFgColor); + Blt_FillBackgroundRectangle(tkwin, pixmap, legendPtr->activeBg, + x, y, legendPtr->entryWidth, legendPtr->entryHeight, + legendPtr->entryBW, legendPtr->activeRelief); + Blt_SetBackgroundOrigin(tkwin, legendPtr->activeBg, + xOrigin, yOrigin); + } else if (isSelected) { + int xOrigin, yOrigin; + Blt_Background bg; + XColor *fg; + + fg = (legendPtr->flags & FOCUS) ? + legendPtr->selInFocusFgColor : legendPtr->selOutFocusFgColor; + bg = (legendPtr->flags & FOCUS) ? + legendPtr->selInFocusBg : legendPtr->selOutFocusBg; + Blt_GetBackgroundOrigin(bg, &xOrigin, &yOrigin); + Blt_SetBackgroundOrigin(tkwin, bg, xOrigin - legendPtr->x, + yOrigin - legendPtr->y); + Blt_Ts_SetForeground(legendPtr->style, fg); + Blt_FillBackgroundRectangle(tkwin, pixmap, bg, x, y, + legendPtr->entryWidth, legendPtr->entryHeight, + legendPtr->selBW, legendPtr->selRelief); + Blt_SetBackgroundOrigin(tkwin, bg, xOrigin, yOrigin); + } else { + Blt_Ts_SetForeground(legendPtr->style, legendPtr->fgColor); + if (elemPtr->legendRelief != TK_RELIEF_FLAT) { + Blt_FillBackgroundRectangle(tkwin, pixmap, graphPtr->normalBg, + x, y, legendPtr->entryWidth, + legendPtr->entryHeight, legendPtr->entryBW, + elemPtr->legendRelief); + } + } + (*elemPtr->procsPtr->drawSymbolProc) (graphPtr, pixmap, elemPtr, + x + xSymbol, y + ySymbol, symbolSize); + Blt_DrawText(tkwin, pixmap, elemPtr->label, &legendPtr->style, + x + xLabel, + y + legendPtr->entryBW + legendPtr->iyPad.side1); + count++; + if (legendPtr->focusPtr == elemPtr) { /* Focus outline */ + if (isSelected) { + XColor *color; + + color = (legendPtr->flags & FOCUS) ? + legendPtr->selInFocusFgColor : + legendPtr->selOutFocusFgColor; + XSetForeground(graphPtr->display, legendPtr->focusGC, + color->pixel); + } + XDrawRectangle(graphPtr->display, pixmap, legendPtr->focusGC, + x + 1, y + 1, legendPtr->entryWidth - 3, + legendPtr->entryHeight - 3); + if (isSelected) { + XSetForeground(graphPtr->display, legendPtr->focusGC, + legendPtr->focusColor->pixel); + } + } + /* Check when to move to the next column */ + if ((count % legendPtr->nRows) > 0) { + y += legendPtr->entryHeight; + } else { + x += legendPtr->entryWidth; + y = yStart; + } + } + /* + * Draw the border and/or background of the legend. + */ + bg = legendPtr->normalBg; + if (bg == NULL) { + bg = graphPtr->normalBg; + } + /* Disable crosshairs before redisplaying to the screen */ + if (legendPtr->site & LEGEND_PLOTAREA_MASK) { + Blt_DisableCrosshairs(graphPtr); + } + Blt_DrawBackgroundRectangle(tkwin, pixmap, bg, 0, 0, w, h, + legendPtr->borderWidth, legendPtr->relief); + XCopyArea(graphPtr->display, pixmap, drawable, graphPtr->drawGC, 0, 0, w, h, + legendPtr->x, legendPtr->y); + if (legendPtr->site & LEGEND_PLOTAREA_MASK) { + Blt_EnableCrosshairs(graphPtr); + } + Tk_FreePixmap(graphPtr->display, pixmap); + graphPtr->flags &= ~DRAW_LEGEND; +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_LegendToPostScript -- + * + *--------------------------------------------------------------------------- + */ +void +Blt_LegendToPostScript(Graph *graphPtr, Blt_Ps ps) +{ + Legend *legendPtr = graphPtr->legend; + double x, y, yStart; + int xLabel, xSymbol, ySymbol; + int count; + Blt_ChainLink link; + int symbolSize, xMid, yMid; + int width, height; + Blt_FontMetrics fontMetrics; + + if ((legendPtr->flags & HIDE) || (legendPtr->nEntries == 0)) { + return; + } + SetLegendOrigin(legendPtr); + + x = legendPtr->x, y = legendPtr->y; + width = legendPtr->width - PADDING(legendPtr->xPad); + height = legendPtr->height - PADDING(legendPtr->yPad); + + Blt_Ps_Append(ps, "% Legend\n"); + graphPtr = legendPtr->graphPtr; + if (graphPtr->pageSetup->flags & PS_DECORATIONS) { + if (legendPtr->normalBg != NULL) { + Tk_3DBorder border; + + border = Blt_BackgroundBorder(legendPtr->normalBg); + Blt_Ps_Fill3DRectangle(ps, border, x, y, width, height, + legendPtr->borderWidth, legendPtr->relief); + } else { + Tk_3DBorder border; + + border = Blt_BackgroundBorder(graphPtr->normalBg); + Blt_Ps_Draw3DRectangle(ps, border, x, y, width, height, + legendPtr->borderWidth, legendPtr->relief); + } + } else { + Blt_Ps_SetClearBackground(ps); + Blt_Ps_XFillRectangle(ps, x, y, width, height); + } + Blt_GetFontMetrics(legendPtr->style.font, &fontMetrics); + symbolSize = fontMetrics.ascent; + xMid = symbolSize + 1 + legendPtr->entryBW; + yMid = (symbolSize / 2) + 1 + legendPtr->entryBW; + xLabel = 2 * symbolSize + legendPtr->entryBW + legendPtr->ixPad.side1 + 5; + xSymbol = xMid + legendPtr->ixPad.side1; + ySymbol = yMid + legendPtr->iyPad.side1; + + x += legendPtr->borderWidth; + y += legendPtr->borderWidth; + Blt_Ps_DrawText(ps, legendPtr->title, &legendPtr->titleStyle, x, y); + if (legendPtr->titleHeight > 0) { + y += legendPtr->titleHeight + legendPtr->yPad.side1; + } + + count = 0; + yStart = y; + for (link = Blt_Chain_FirstLink(graphPtr->elements.displayList); + link != NULL; link = Blt_Chain_NextLink(link)) { + Element *elemPtr; + + elemPtr = Blt_Chain_GetValue(link); + if (elemPtr->label == NULL) { + continue; /* Skip this label */ + } + if (elemPtr->flags & LABEL_ACTIVE) { + Tk_3DBorder border; + + border = Blt_BackgroundBorder(legendPtr->activeBg); + Blt_Ts_SetForeground(legendPtr->style, legendPtr->activeFgColor); + Blt_Ps_Fill3DRectangle(ps, border, x, y, legendPtr->entryWidth, + legendPtr->entryHeight, legendPtr->entryBW, + legendPtr->activeRelief); + } else { + Blt_Ts_SetForeground(legendPtr->style, legendPtr->fgColor); + if (elemPtr->legendRelief != TK_RELIEF_FLAT) { + Tk_3DBorder border; + + border = Blt_BackgroundBorder(graphPtr->normalBg); + Blt_Ps_Draw3DRectangle(ps, border, x, y, legendPtr->entryWidth, + legendPtr->entryHeight, legendPtr->entryBW, + elemPtr->legendRelief); + } + } + (*elemPtr->procsPtr->printSymbolProc) (graphPtr, ps, elemPtr, + x + xSymbol, y + ySymbol, symbolSize); + Blt_Ps_DrawText(ps, elemPtr->label, &legendPtr->style, + x + xLabel, y + legendPtr->entryBW + legendPtr->iyPad.side1); + count++; + if ((count % legendPtr->nRows) > 0) { + y += legendPtr->entryHeight; + } else { + x += legendPtr->entryWidth; + y = yStart; + } + } +} + +/* + *--------------------------------------------------------------------------- + * + * DisplayLegend -- + * + *--------------------------------------------------------------------------- + */ +static void +DisplayLegend(ClientData clientData) +{ + Legend *legendPtr = clientData; + Graph *graphPtr; + + legendPtr->flags &= ~REDRAW_PENDING; + if (legendPtr->tkwin == NULL) { + return; /* Window has been destroyed. */ + } + graphPtr = legendPtr->graphPtr; + if (legendPtr->site == LEGEND_WINDOW) { + int w, h; + + w = Tk_Width(legendPtr->tkwin); + h = Tk_Height(legendPtr->tkwin); + if ((w != legendPtr->width) || (h != legendPtr->height)) { + Blt_MapLegend(graphPtr, w, h); + } + } + if (Tk_IsMapped(legendPtr->tkwin)) { + Blt_DrawLegend(graphPtr, Tk_WindowId(legendPtr->tkwin)); + } +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_ConfigureLegend -- + * + * Routine to configure the legend. + * + * Results: + * A standard TCL result. + * + * Side Effects: + * Graph will be redrawn to reflect the new legend attributes. + * + *--------------------------------------------------------------------------- + */ +void +Blt_ConfigureLegend(Graph *graphPtr) +{ + GC newGC; + XGCValues gcValues; + unsigned long gcMask; + Legend *legendPtr; + + legendPtr = graphPtr->legend; + /* GC for active label. Dashed outline. */ + gcMask = GCForeground | GCLineStyle; + gcValues.foreground = legendPtr->focusColor->pixel; + gcValues.line_style = (LineIsDashed(legendPtr->focusDashes)) + ? LineOnOffDash : LineSolid; + newGC = Blt_GetPrivateGC(legendPtr->tkwin, gcMask, &gcValues); + if (LineIsDashed(legendPtr->focusDashes)) { + legendPtr->focusDashes.offset = 2; + Blt_SetDashes(graphPtr->display, newGC, &legendPtr->focusDashes); + } + if (legendPtr->focusGC != NULL) { + Blt_FreePrivateGC(graphPtr->display, legendPtr->focusGC); + } + legendPtr->focusGC = newGC; + + /* + * Update the layout of the graph (and redraw the elements) if any of + * the following legend options (all of which affect the size of the + * legend) have changed. + * + * -activeborderwidth, -borderwidth + * -border + * -font + * -hide + * -ipadx, -ipady, -padx, -pady + * -rows + * + * If the position of the legend changed to/from the default + * position, also indicate that a new layout is needed. + * + */ + if (legendPtr->site == LEGEND_WINDOW) { + Blt_Legend_EventuallyRedraw(graphPtr); + } else if (Blt_ConfigModified(configSpecs, "-*border*", "-*pad?", + "-hide", "-font", "-rows", (char *)NULL)) { + graphPtr->flags |= RESET_WORLD; + graphPtr->flags |= (REDRAW_WORLD | CACHE_DIRTY); + Blt_EventuallyRedrawGraph(graphPtr); + } +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_DestroyLegend -- + * + * Results: + * None. + * + * Side effects: + * Resources associated with the legend are freed. + * + *--------------------------------------------------------------------------- + */ +void +Blt_DestroyLegend(Graph *graphPtr) +{ + Legend *legendPtr = graphPtr->legend; + + if (graphPtr->legend == NULL) { + return; + } + + Blt_FreeOptions(configSpecs, (char *)legendPtr, graphPtr->display, 0); + Blt_Ts_FreeStyle(graphPtr->display, &legendPtr->style); + Blt_Ts_FreeStyle(graphPtr->display, &legendPtr->titleStyle); + Blt_DestroyBindingTable(legendPtr->bindTable); + + if (legendPtr->focusGC != NULL) { + Blt_FreePrivateGC(graphPtr->display, legendPtr->focusGC); + } + if (legendPtr->timerToken != NULL) { + Tcl_DeleteTimerHandler(legendPtr->timerToken); + } + if (legendPtr->tkwin != NULL) { + Tk_DeleteSelHandler(legendPtr->tkwin, XA_PRIMARY, XA_STRING); + } + if (legendPtr->site == LEGEND_WINDOW) { + Tk_Window tkwin; + + /* The graph may be in the process of being torn down */ + if (legendPtr->cmdToken != NULL) { + Tcl_DeleteCommandFromToken(graphPtr->interp, legendPtr->cmdToken); + } + if (legendPtr->flags & REDRAW_PENDING) { + Tcl_CancelIdleCall(DisplayLegend, legendPtr); + legendPtr->flags &= ~REDRAW_PENDING; + } + tkwin = legendPtr->tkwin; + legendPtr->tkwin = NULL; + if (tkwin != NULL) { + Tk_DeleteEventHandler(tkwin, ExposureMask | StructureNotifyMask, + LegendEventProc, graphPtr); + Blt_DeleteWindowInstanceData(tkwin); + Tk_DestroyWindow(tkwin); + } + } + Blt_Free(legendPtr); +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_CreateLegend -- + * + * Creates and initializes a legend structure with default settings + * + * Results: + * A standard TCL result. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +int +Blt_CreateLegend(Graph *graphPtr) +{ + Legend *legendPtr; + + legendPtr = Blt_AssertCalloc(1, sizeof(Legend)); + graphPtr->legend = legendPtr; + legendPtr->graphPtr = graphPtr; + legendPtr->tkwin = graphPtr->tkwin; + legendPtr->xReq = legendPtr->yReq = -SHRT_MAX; + legendPtr->relief = TK_RELIEF_SUNKEN; + legendPtr->activeRelief = TK_RELIEF_FLAT; + legendPtr->entryBW = 2; + legendPtr->borderWidth = 2; + legendPtr->ixPad.side1 = legendPtr->ixPad.side2 = 1; + legendPtr->iyPad.side1 = legendPtr->iyPad.side2 = 1; + legendPtr->xPad.side1 = legendPtr->xPad.side2 = 1; + legendPtr->yPad.side1 = legendPtr->yPad.side2 = 1; + legendPtr->anchor = TK_ANCHOR_N; + legendPtr->site = LEGEND_RIGHT; + legendPtr->selectMode = SELECT_MODE_MULTIPLE; + Blt_Ts_InitStyle(legendPtr->style); + Blt_Ts_InitStyle(legendPtr->titleStyle); + legendPtr->style.justify = TK_JUSTIFY_LEFT; + legendPtr->style.anchor = TK_ANCHOR_NW; + legendPtr->titleStyle.justify = TK_JUSTIFY_LEFT; + legendPtr->titleStyle.anchor = TK_ANCHOR_NW; + legendPtr->bindTable = Blt_CreateBindingTable(graphPtr->interp, + graphPtr->tkwin, graphPtr, PickEntryProc, Blt_GraphTags); + + Blt_InitHashTable(&legendPtr->selectTable, BLT_ONE_WORD_KEYS); + legendPtr->selected = Blt_Chain_Create(); + Tk_CreateSelHandler(legendPtr->tkwin, XA_PRIMARY, XA_STRING, + SelectionProc, legendPtr, XA_STRING); + legendPtr->selRelief = TK_RELIEF_FLAT; + legendPtr->selBW = 1; + legendPtr->onTime = 600; + legendPtr->offTime = 300; + if (Blt_ConfigureComponentFromObj(graphPtr->interp, graphPtr->tkwin, + "legend", "Legend", configSpecs, 0, (Tcl_Obj **)NULL, + (char *)legendPtr, 0) != TCL_OK) { + return TCL_ERROR; + } + Blt_ConfigureLegend(graphPtr); + return TCL_OK; +} + +static Element * +GetNextRow(Graph *graphPtr, Element *focusPtr) +{ + Blt_ChainLink link; + int row, col; + + col = focusPtr->col; + row = focusPtr->row + 1; + for (link = focusPtr->link; link != NULL; link = Blt_Chain_NextLink(link)) { + Element *elemPtr; + + elemPtr = Blt_Chain_GetValue(link); + if (elemPtr->label == NULL) { + continue; + } + if ((elemPtr->col == col) && (elemPtr->row == row)) { + return elemPtr; + } + } + return NULL; +} + +static Element * +GetNextColumn(Graph *graphPtr, Element *focusPtr) +{ + Blt_ChainLink link; + int row, col; + + col = focusPtr->col + 1; + row = focusPtr->row; + for (link = focusPtr->link; link != NULL; link = Blt_Chain_NextLink(link)) { + Element *elemPtr; + + elemPtr = Blt_Chain_GetValue(link); + if (elemPtr->label == NULL) { + continue; + } + if ((elemPtr->col == col) && (elemPtr->row == row)) { + return elemPtr; /* Don't go beyond legend boundaries. */ + } + } + return NULL; +} + +static Element * +GetPreviousRow(Graph *graphPtr, Element *focusPtr) +{ + Blt_ChainLink link; + int row, col; + + col = focusPtr->col; + row = focusPtr->row - 1; + for (link = focusPtr->link; link != NULL; link = Blt_Chain_PrevLink(link)) { + Element *elemPtr; + + elemPtr = Blt_Chain_GetValue(link); + if (elemPtr->label == NULL) { + continue; + } + if ((elemPtr->col == col) && (elemPtr->row == row)) { + return elemPtr; + } + } + return NULL; +} + +static Element * +GetPreviousColumn(Graph *graphPtr, Element *focusPtr) +{ + Blt_ChainLink link; + int row, col; + + col = focusPtr->col - 1; + row = focusPtr->row; + for (link = focusPtr->link; link != NULL; link = Blt_Chain_PrevLink(link)) { + Element *elemPtr; + + elemPtr = Blt_Chain_GetValue(link); + if (elemPtr->label == NULL) { + continue; + } + if ((elemPtr->col == col) && (elemPtr->row == row)) { + return elemPtr; + } + } + return NULL; +} + +static Element * +GetFirstElement(Graph *graphPtr) +{ + Blt_ChainLink link; + + for (link = Blt_Chain_FirstLink(graphPtr->elements.displayList); + link != NULL; link = Blt_Chain_NextLink(link)) { + Element *elemPtr; + + elemPtr = Blt_Chain_GetValue(link); + if (elemPtr->label != NULL) { + return elemPtr; + } + } + return NULL; +} + +static Element * +GetLastElement(Graph *graphPtr) +{ + Blt_ChainLink link; + + for (link = Blt_Chain_LastLink(graphPtr->elements.displayList); + link != NULL; link = Blt_Chain_PrevLink(link)) { + Element *elemPtr; + + elemPtr = Blt_Chain_GetValue(link); + if (elemPtr->label != NULL) { + return elemPtr; + } + } + return NULL; +} + +/* + *--------------------------------------------------------------------------- + * + * GetElementFromObj -- + * + * Parse an index into an entry and return either its value or an error. + * + * Results: + * A standard TCL result. If all went well, then *indexPtr is filled in + * with the character index (into entryPtr) corresponding to string. The + * index value is guaranteed to lie between 0 and the number of characters + * in the string, inclusive. If an error occurs then an error message is + * left in the interp's result. + * + * Side effects: + * None. + * + *--------------------------------------------------------------------------- + */ +static int +GetElementFromObj(Graph *graphPtr, Tcl_Obj *objPtr, Element **elemPtrPtr) +{ + Element *elemPtr; + Legend *legendPtr; + Tcl_Interp *interp; + char c; + const char *string; + int last; + int index; + + legendPtr = graphPtr->legend; + interp = graphPtr->interp; + string = Tcl_GetString(objPtr); + c = string[0]; + elemPtr = NULL; + + last = Blt_Chain_GetLength(graphPtr->elements.displayList) - 1; + if ((c == 'a') && (strcmp(string, "anchor") == 0)) { + elemPtr = legendPtr->selAnchorPtr; + } else if ((c == 'c') && (strcmp(string, "current") == 0)) { + elemPtr = (Element *)Blt_GetCurrentItem(legendPtr->bindTable); + } else if ((c == 'f') && (strcmp(string, "first") == 0)) { + elemPtr = GetFirstElement(graphPtr); + } else if ((c == 'f') && (strcmp(string, "focus") == 0)) { + elemPtr = legendPtr->focusPtr; + } else if ((c == 'l') && (strcmp(string, "last") == 0)) { + elemPtr = GetLastElement(graphPtr); + } else if ((c == 'e') && (strcmp(string, "end") == 0)) { + elemPtr = GetLastElement(graphPtr); + } else if ((c == 'n') && (strcmp(string, "next.row") == 0)) { + elemPtr = GetNextRow(graphPtr, legendPtr->focusPtr); + } else if ((c == 'n') && (strcmp(string, "next.column") == 0)) { + elemPtr = GetNextColumn(graphPtr, legendPtr->focusPtr); + } else if ((c == 'p') && (strcmp(string, "previous.row") == 0)) { + elemPtr = GetPreviousRow(graphPtr, legendPtr->focusPtr); + } else if ((c == 'p') && (strcmp(string, "previous.column") == 0)) { + elemPtr = GetPreviousColumn(graphPtr, legendPtr->focusPtr); + } else if ((c == 's') && (strcmp(string, "sel.first") == 0)) { + elemPtr = legendPtr->selFirstPtr; + } else if ((c == 's') && (strcmp(string, "sel.last") == 0)) { + elemPtr = legendPtr->selLastPtr; + } else if (c == '@') { + int x, y; + + if (Blt_GetXY(interp, legendPtr->tkwin, string, &x, &y) != TCL_OK) { + return TCL_ERROR; + } + elemPtr = (Element *)PickEntryProc(graphPtr, x, y, NULL); + } else { + if (Blt_GetElement(interp, graphPtr, objPtr, &elemPtr) != TCL_OK) { + return TCL_ERROR; + } + if (elemPtr->link == NULL) { + Tcl_AppendResult(interp, "bad legend index \"", string, "\"", + (char *)NULL); + return TCL_ERROR; + } + if (elemPtr->label == NULL) { + elemPtr = NULL; + } + } + *elemPtrPtr = elemPtr; + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * SelectRange -- + * + * Sets the selection flag for a range of nodes. The range is determined + * by two pointers which designate the first/last nodes of the range. + * + * Results: + * Always returns TCL_OK. + * + *--------------------------------------------------------------------------- + */ +static int +SelectRange(Legend *legendPtr, Element *fromPtr, Element *toPtr) +{ + + if (Blt_Chain_IsBefore(fromPtr->link, toPtr->link)) { + Blt_ChainLink link; + + for (link = fromPtr->link; link != NULL; + link = Blt_Chain_NextLink(link)) { + Element *elemPtr; + + elemPtr = Blt_Chain_GetValue(link); + SelectEntry(legendPtr, elemPtr); + if (link == toPtr->link) { + break; + } + } + } else { + Blt_ChainLink link; + + for (link = fromPtr->link; link != NULL; + link = Blt_Chain_PrevLink(link)) { + Element *elemPtr; + + elemPtr = Blt_Chain_GetValue(link); + SelectEntry(legendPtr, elemPtr); + if (link == toPtr->link) { + break; + } + } + } + return TCL_OK; +} + + +#ifdef notdef +/* + *--------------------------------------------------------------------------- + * + * SelectText -- + * + * Modify the selection by moving its un-anchored end. This could make + * the selection either larger or smaller. + * + * Results: + * None. + * + * Side effects: + * The selection changes. + * + *--------------------------------------------------------------------------- + */ +static int +SelectText(Legend *legendPtr, Element *elemPtr) +{ + Element *firstPtr, *lastPtr; + Graph *graphPtr = legendPtr->graphPtr; + + /* Grab the selection if we don't own it already. */ + if ((legendPtr->flags&SELECT_EXPORT) && (legendPtr->selFirstPtr == NULL)) { + Tk_OwnSelection(legendPtr->tkwin, XA_PRIMARY, LostSelectionProc, + legendPtr); + } + /* If the anchor hasn't been set, assume the beginning of the legend. */ + if (legendPtr->selAnchorPtr == NULL) { + legendPtr->selAnchorPtr = GetFirstElement(graphPtr); + } + if (legendPtr->selAnchorPtr != elemPtr) { + firstPtr = legendPtr->selAnchorPtr; + lastPtr = elemPtr; + } else { + firstPtr = elemPtr; + lastPtr = legendPtr->selAnchorPtr; + } + if ((legendPtr->selFirstPtr != firstPtr) || + (legendPtr->selLastPtr != lastPtr)) { + legendPtr->selFirstPtr = firstPtr; + legendPtr->selLastPtr = lastPtr; + SelectRange(legendPtr, firstPtr, lastPtr); + Blt_Legend_EventuallyRedraw(graphPtr); + } + return TCL_OK; +} +#endif + +/* + *--------------------------------------------------------------------------- + * + * ActivateOp -- + * + * Activates a particular label in the legend. + * + * Results: + * A standard TCL result. + * + * Side Effects: + * Graph will be redrawn to reflect the new legend attributes. + * + *--------------------------------------------------------------------------- + */ +static int +ActivateOp(Graph *graphPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) +{ + Legend *legendPtr = graphPtr->legend; + unsigned int active, redraw; + const char *string; + int i; + + string = Tcl_GetString(objv[2]); + active = (string[0] == 'a') ? LABEL_ACTIVE : 0; + redraw = FALSE; + for (i = 3; i < objc; i++) { + Blt_ChainLink link; + const char *pattern; + + pattern = Tcl_GetString(objv[i]); + for (link = Blt_Chain_FirstLink(graphPtr->elements.displayList); + link != NULL; link = Blt_Chain_NextLink(link)) { + Element *elemPtr; + + elemPtr = Blt_Chain_GetValue(link); + if (Tcl_StringMatch(elemPtr->obj.name, pattern)) { + fprintf(stderr, "legend %s(%s) %s is currently %d\n", + string, pattern, elemPtr->obj.name, + (elemPtr->flags & LABEL_ACTIVE)); + if (active) { + if ((elemPtr->flags & LABEL_ACTIVE) == 0) { + elemPtr->flags |= LABEL_ACTIVE; + redraw = TRUE; + } + } else { + if (elemPtr->flags & LABEL_ACTIVE) { + elemPtr->flags &= ~LABEL_ACTIVE; + redraw = TRUE; + } + } + fprintf(stderr, "legend %s(%s) %s is now %d\n", + string, pattern, elemPtr->obj.name, + (elemPtr->flags & LABEL_ACTIVE)); + } + } + } + if ((redraw) && ((legendPtr->flags & HIDE) == 0)) { + /* + * See if how much we need to draw. If the graph is already scheduled + * for a redraw, just make sure the right flags are set. Otherwise + * redraw only the legend: it's either in an external window or it's + * the only thing that need updating. + */ + if ((legendPtr->site != LEGEND_WINDOW) && + (graphPtr->flags & REDRAW_PENDING)) { + graphPtr->flags |= CACHE_DIRTY; + graphPtr->flags |= REDRAW_WORLD; /* Redraw entire graph. */ + } else { + Blt_Legend_EventuallyRedraw(graphPtr); + } + } + { + Blt_ChainLink link; + Tcl_Obj *listObjPtr; + + listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL); + /* List active elements in stacking order. */ + for (link = Blt_Chain_FirstLink(graphPtr->elements.displayList); + link != NULL; link = Blt_Chain_NextLink(link)) { + Element *elemPtr; + + elemPtr = Blt_Chain_GetValue(link); + if (elemPtr->flags & LABEL_ACTIVE) { + Tcl_Obj *objPtr; + + objPtr = Tcl_NewStringObj(elemPtr->obj.name, -1); + Tcl_ListObjAppendElement(interp, listObjPtr, objPtr); + } + } + Tcl_SetObjResult(interp, listObjPtr); + } + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * BindOp -- + * + * .t bind index sequence command + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +BindOp(Graph *graphPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) +{ + if (objc == 3) { + Blt_HashEntry *hPtr; + Blt_HashSearch iter; + Tcl_Obj *listObjPtr; + + listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL); + for (hPtr = Blt_FirstHashEntry(&graphPtr->elements.tagTable, &iter); + hPtr != NULL; hPtr = Blt_NextHashEntry(&iter)) { + const char *tagName; + Tcl_Obj *objPtr; + + tagName = Blt_GetHashKey(&graphPtr->elements.tagTable, hPtr); + objPtr = Tcl_NewStringObj(tagName, -1); + Tcl_ListObjAppendElement(interp, listObjPtr, objPtr); + } + Tcl_SetObjResult(interp, listObjPtr); + return TCL_OK; + } + return Blt_ConfigureBindingsFromObj(interp, graphPtr->legend->bindTable, + Blt_MakeElementTag(graphPtr, Tcl_GetString(objv[3])), objc - 4, + objv + 4); +} + +/* + *--------------------------------------------------------------------------- + * + * CgetOp -- + * + * Queries or resets options for the legend. + * + * Results: + * A standard TCL result. + * + * Side Effects: + * Graph will be redrawn to reflect the new legend attributes. + * + *--------------------------------------------------------------------------- + */ +/* ARGSUSED */ +static int +CgetOp(Graph *graphPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) +{ + return Blt_ConfigureValueFromObj(interp, graphPtr->tkwin, configSpecs, + (char *)graphPtr->legend, objv[3], 0); +} + +/* + *--------------------------------------------------------------------------- + * + * ConfigureOp -- + * + * Queries or resets options for the legend. + * + * Results: + * A standard TCL result. + * + * Side Effects: + * Graph will be redrawn to reflect the new legend attributes. + * + *--------------------------------------------------------------------------- + */ +static int +ConfigureOp(Graph *graphPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) +{ + int flags = BLT_CONFIG_OBJV_ONLY; + Legend *legendPtr; + + legendPtr = graphPtr->legend; + if (objc == 3) { + return Blt_ConfigureInfoFromObj(interp, graphPtr->tkwin, configSpecs, + (char *)legendPtr, (Tcl_Obj *)NULL, flags); + } else if (objc == 4) { + return Blt_ConfigureInfoFromObj(interp, graphPtr->tkwin, configSpecs, + (char *)legendPtr, objv[3], flags); + } + if (Blt_ConfigureWidgetFromObj(interp, graphPtr->tkwin, configSpecs, + objc - 3, objv + 3, (char *)legendPtr, flags) != TCL_OK) { + return TCL_ERROR; + } + Blt_ConfigureLegend(graphPtr); + return TCL_OK; +} + + +/*ARGSUSED*/ +static int +CurselectionOp(Graph *graphPtr, Tcl_Interp *interp, int objc, + Tcl_Obj *const *objv) +{ + Legend *legendPtr = graphPtr->legend; + Tcl_Obj *listObjPtr; + + listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL); + if (legendPtr->flags & SELECT_SORTED) { + Blt_ChainLink link; + + for (link = Blt_Chain_FirstLink(legendPtr->selected); link != NULL; + link = Blt_Chain_NextLink(link)) { + Element *elemPtr; + Tcl_Obj *objPtr; + + elemPtr = Blt_Chain_GetValue(link); + objPtr = Tcl_NewStringObj(elemPtr->obj.name, -1); + Tcl_ListObjAppendElement(interp, listObjPtr, objPtr); + } + } else { + Blt_ChainLink link; + + /* List of selected entries is in stacking order. */ + for (link = Blt_Chain_FirstLink(graphPtr->elements.displayList); + link != NULL; link = Blt_Chain_NextLink(link)) { + Element *elemPtr; + + elemPtr = Blt_Chain_GetValue(link); + + if (EntryIsSelected(legendPtr, elemPtr)) { + Tcl_Obj *objPtr; + + objPtr = Tcl_NewStringObj(elemPtr->obj.name, -1); + Tcl_ListObjAppendElement(interp, listObjPtr, objPtr); + } + } + } + Tcl_SetObjResult(interp, listObjPtr); + return TCL_OK; +} + +/*ARGSUSED*/ +static int +FocusOp(Graph *graphPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) +{ + Legend *legendPtr = graphPtr->legend; + + if (objc == 4) { + Element *elemPtr; + + if (GetElementFromObj(graphPtr, objv[3], &elemPtr) != TCL_OK) { + return TCL_ERROR; + } + if ((elemPtr != NULL) && (elemPtr != legendPtr->focusPtr)) { + /* Changing focus can only affect the visible entries. The entry + * layout stays the same. */ + legendPtr->focusPtr = elemPtr; + } + Blt_SetFocusItem(legendPtr->bindTable, legendPtr->focusPtr, + CID_LEGEND_ENTRY); + Blt_Legend_EventuallyRedraw(graphPtr); + } + if (legendPtr->focusPtr != NULL) { + Tcl_SetStringObj(Tcl_GetObjResult(interp), + legendPtr->focusPtr->obj.name, -1); + } + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * GetOp -- + * + * Find the legend entry from the given argument. The argument can be + * either a screen position "@x,y" or the name of an element. + * + * I don't know how useful it is to test with the name of an element. + * + * Results: + * A standard TCL result. + * + * Side Effects: + * Graph will be redrawn to reflect the new legend attributes. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +GetOp(Graph *graphPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) +{ + Legend *legendPtr = graphPtr->legend; + + if (((legendPtr->flags & HIDE) == 0) && (legendPtr->nEntries > 0)) { + Element *elemPtr; + + if (GetElementFromObj(graphPtr, objv[3], &elemPtr) != TCL_OK) { + return TCL_ERROR; + } + if (elemPtr != NULL) { + Tcl_SetStringObj(Tcl_GetObjResult(interp), elemPtr->obj.name,-1); + } + } + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * IconOp -- + * + * Find the legend entry from the given argument. The argument + * can be either a screen position "@x,y" or the name of an + * element. + * + * I don't know how useful it is to test with the name of an + * element. + * + * Results: + * A standard Tcl result. + * + * Side Effects: + * Graph will be redrawn to reflect the new legend attributes. + * + * .g legend icon elemName image + * + *---------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +IconOp(Graph *graphPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) +{ + Blt_Picture picture; + Element *elemPtr; + Legend *legendPtr = graphPtr->legend; + Pixmap pixmap; + Blt_FontMetrics fontMetrics; + Tk_PhotoHandle photo; + const char *imageName; + int isPicture; + int w, h, x, y, s; + + if (GetElementFromObj(graphPtr, objv[3], &elemPtr) != TCL_OK) { + return TCL_ERROR; + } + if (elemPtr == NULL) { + return TCL_OK; /* Unknown index. */ + } + imageName = Tcl_GetString(objv[4]); + photo = Tk_FindPhoto(interp, imageName); + if (photo != NULL) { + isPicture = FALSE; + } else if (Blt_GetPicture(interp, imageName, &picture) == TCL_OK) { + isPicture = TRUE; + } else { + return TCL_ERROR; + } + Blt_GetFontMetrics(legendPtr->style.font, &fontMetrics); + s = fontMetrics.ascent; + h = s + PADDING(legendPtr->iyPad) + 1; + w = s + s + 1 + PADDING(legendPtr->ixPad); + x = (w / 2); + y = (h / 2); + + pixmap = Tk_GetPixmap(graphPtr->display, Tk_RootWindow(graphPtr->tkwin), + w, h, Tk_Depth(graphPtr->tkwin)); + Blt_FillBackgroundRectangle(graphPtr->tkwin, pixmap, graphPtr->plotBg, + 0, 0, w, h, TK_RELIEF_FLAT, 0); + (*elemPtr->procsPtr->drawSymbolProc) (graphPtr, pixmap, elemPtr, x, y, s); + picture = Blt_DrawableToPicture(graphPtr->tkwin, pixmap, 0, 0, w, h, 1.0); + Tk_FreePixmap(graphPtr->display, pixmap); + if (picture == NULL) { + Tcl_AppendResult(interp, "can't get picture of symbol.", (char *)NULL); + return TCL_ERROR; + } + /* Make the background transparent. */ + { + int y; + Blt_Pixel bg; + Blt_Pixel *destRowPtr; + XColor *colorPtr; + + colorPtr = Blt_BackgroundBorderColor(graphPtr->plotBg); + bg.Red = colorPtr->red >> 8; + bg.Green = colorPtr->green >> 8; + bg.Blue = colorPtr->blue >> 8; + bg.Alpha = 0xFF; + + destRowPtr = Blt_PictureBits(picture); + for (y = 0; y < h; y++) { + Blt_Pixel *dp, *dend; + + for (dp = destRowPtr, dend = dp + w; dp < dend; dp++) { + if (dp->u32 == bg.u32) { + dp->Alpha = 0x0; + } + } + destRowPtr += Blt_PictureStride(picture); + } + } + Blt_ClassifyPicture(picture); + if (isPicture) { + Blt_ResetPicture(interp, imageName, picture); + } else { + Blt_PictureToPhoto(picture, photo); + Blt_FreePicture(picture); + } + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * SelectionAnchorOp -- + * + * Sets the selection anchor to the element given by a index. The + * selection anchor is the end of the selection that is fixed while + * dragging out a selection with the mouse. The index "anchor" may be + * used to refer to the anchor element. + * + * Results: + * None. + * + * Side effects: + * The selection changes. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +SelectionAnchorOp(Graph *graphPtr, Tcl_Interp *interp, int objc, + Tcl_Obj *const *objv) +{ + Legend *legendPtr = graphPtr->legend; + Element *elemPtr; + + if (GetElementFromObj(graphPtr, objv[4], &elemPtr) != TCL_OK) { + return TCL_ERROR; + } + /* Set both the anchor and the mark. Indicates that a single entry + * is selected. */ + legendPtr->selAnchorPtr = elemPtr; + legendPtr->selMarkPtr = NULL; + if (elemPtr != NULL) { + Tcl_SetStringObj(Tcl_GetObjResult(interp), elemPtr->obj.name, -1); + } + Blt_Legend_EventuallyRedraw(graphPtr); + return TCL_OK; +} + + +/* + *--------------------------------------------------------------------------- + * + * SelectionClearallOp + * + * Clears the entire selection. + * + * Results: + * None. + * + * Side effects: + * The selection changes. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +SelectionClearallOp(Graph *graphPtr, Tcl_Interp *interp, int objc, + Tcl_Obj *const *objv) +{ + Legend *legendPtr = graphPtr->legend; + + ClearSelection(legendPtr); + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * SelectionIncludesOp + * + * Returns 1 if the element indicated by index is currently + * selected, 0 if it isn't. + * + * Results: + * None. + * + * Side effects: + * The selection changes. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +SelectionIncludesOp(Graph *graphPtr, Tcl_Interp *interp, int objc, + Tcl_Obj *const *objv) +{ + Legend *legendPtr = graphPtr->legend; + Element *elemPtr; + int bool; + + if (GetElementFromObj(graphPtr, objv[4], &elemPtr) != TCL_OK) { + return TCL_ERROR; + } + bool = EntryIsSelected(legendPtr, elemPtr); + Tcl_SetBooleanObj(Tcl_GetObjResult(interp), bool); + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * SelectionMarkOp -- + * + * Sets the selection mark to the element given by a index. The + * selection anchor is the end of the selection that is movable while + * dragging out a selection with the mouse. The index "mark" may be used + * to refer to the anchor element. + * + * Results: + * None. + * + * Side effects: + * The selection changes. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +SelectionMarkOp(Graph *graphPtr, Tcl_Interp *interp, int objc, + Tcl_Obj *const *objv) +{ + Legend *legendPtr = graphPtr->legend; + Element *elemPtr; + + if (GetElementFromObj(graphPtr, objv[4], &elemPtr) != TCL_OK) { + return TCL_ERROR; + } + if (legendPtr->selAnchorPtr == NULL) { + Tcl_AppendResult(interp, "selection anchor must be set first", + (char *)NULL); + return TCL_ERROR; + } + if (legendPtr->selMarkPtr != elemPtr) { + Blt_ChainLink link, next; + + /* Deselect entry from the list all the way back to the anchor. */ + for (link = Blt_Chain_LastLink(legendPtr->selected); link != NULL; + link = next) { + Element *selectPtr; + + next = Blt_Chain_PrevLink(link); + selectPtr = Blt_Chain_GetValue(link); + if (selectPtr == legendPtr->selAnchorPtr) { + break; + } + DeselectElement(legendPtr, selectPtr); + } + legendPtr->flags &= ~SELECT_MASK; + legendPtr->flags |= SELECT_SET; + SelectRange(legendPtr, legendPtr->selAnchorPtr, elemPtr); + Tcl_SetStringObj(Tcl_GetObjResult(interp), elemPtr->obj.name, -1); + legendPtr->selMarkPtr = elemPtr; + + Blt_Legend_EventuallyRedraw(graphPtr); + if (legendPtr->selectCmd != NULL) { + EventuallyInvokeSelectCmd(legendPtr); + } + } + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * SelectionPresentOp + * + * Returns 1 if there is a selection and 0 if it isn't. + * + * Results: + * A standard TCL result. interp->result will contain a boolean string + * indicating if there is a selection. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +SelectionPresentOp(Graph *graphPtr, Tcl_Interp *interp, int objc, + Tcl_Obj *const *objv) +{ + Legend *legendPtr = graphPtr->legend; + int bool; + + bool = (Blt_Chain_GetLength(legendPtr->selected) > 0); + Tcl_SetBooleanObj(Tcl_GetObjResult(interp), bool); + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * SelectionSetOp + * + * Selects, deselects, or toggles all of the elements in the range + * between first and last, inclusive, without affecting the selection + * state of elements outside that range. + * + * Results: + * None. + * + * Side effects: + * The selection changes. + * + * .g legend selection set first last + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +SelectionSetOp(Graph *graphPtr, Tcl_Interp *interp, int objc, + Tcl_Obj *const *objv) +{ + Legend *legendPtr = graphPtr->legend; + Element *firstPtr, *lastPtr; + const char *string; + + legendPtr->flags &= ~SELECT_MASK; + string = Tcl_GetString(objv[3]); + switch (string[0]) { + case 's': + legendPtr->flags |= SELECT_SET; + break; + case 'c': + legendPtr->flags |= SELECT_CLEAR; + break; + case 't': + legendPtr->flags |= SELECT_TOGGLE; + break; + } + if (GetElementFromObj(graphPtr, objv[4], &firstPtr) != TCL_OK) { + return TCL_ERROR; + } + if ((firstPtr->flags & HIDE) && ((legendPtr->flags & SELECT_CLEAR)==0)) { + Tcl_AppendResult(interp, "can't select hidden node \"", + Tcl_GetString(objv[4]), "\"", (char *)NULL); + return TCL_ERROR; + } + lastPtr = firstPtr; + if (objc > 5) { + if (GetElementFromObj(graphPtr, objv[5], &lastPtr) != TCL_OK) { + return TCL_ERROR; + } + if ((lastPtr->flags & HIDE) && + ((legendPtr->flags & SELECT_CLEAR) == 0)) { + Tcl_AppendResult(interp, "can't select hidden node \"", + Tcl_GetString(objv[5]), "\"", (char *)NULL); + return TCL_ERROR; + } + } + if (firstPtr == lastPtr) { + SelectEntry(legendPtr, firstPtr); + } else { + SelectRange(legendPtr, firstPtr, lastPtr); + } + /* Set both the anchor and the mark. Indicates that a single entry is + * selected. */ + if (legendPtr->selAnchorPtr == NULL) { + legendPtr->selAnchorPtr = firstPtr; + } + if (legendPtr->flags & SELECT_EXPORT) { + Tk_OwnSelection(legendPtr->tkwin, XA_PRIMARY, LostSelectionProc, + legendPtr); + } + Blt_Legend_EventuallyRedraw(graphPtr); + if (legendPtr->selectCmd != NULL) { + EventuallyInvokeSelectCmd(legendPtr); + } + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * SelectionOp -- + * + * This procedure handles the individual options for text selections. + * The selected text is designated by start and end indices into the text + * pool. The selected segment has both a anchored and unanchored ends. + * + * Results: + * None. + * + * Side effects: + * The selection changes. + * + * .g legend selection anchor + *--------------------------------------------------------------------------- + */ +static Blt_OpSpec selectionOps[] = +{ + {"anchor", 1, SelectionAnchorOp, 5, 5, "elem",}, + {"clear", 5, SelectionSetOp, 5, 6, "firstElem ?lastElem?",}, + {"clearall", 6, SelectionClearallOp, 4, 4, "",}, + {"includes", 1, SelectionIncludesOp, 5, 5, "elem",}, + {"mark", 1, SelectionMarkOp, 5, 5, "elem",}, + {"present", 1, SelectionPresentOp, 4, 4, "",}, + {"set", 1, SelectionSetOp, 5, 6, "firstElem ?lastElem?",}, + {"toggle", 1, SelectionSetOp, 5, 6, "firstElem ?lastElem?",}, +}; +static int nSelectionOps = sizeof(selectionOps) / sizeof(Blt_OpSpec); + +static int +SelectionOp(Graph *graphPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) +{ + GraphLegendProc *proc; + int result; + + proc = Blt_GetOpFromObj(interp, nSelectionOps, selectionOps, BLT_OP_ARG3, + objc, objv, 0); + if (proc == NULL) { + return TCL_ERROR; + } + result = (*proc) (graphPtr, interp, objc, objv); + return result; +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_LegendOp -- + * + * Results: + * A standard TCL result. + * + * Side Effects: + * Legend is possibly redrawn. + * + *--------------------------------------------------------------------------- + */ + +static Blt_OpSpec legendOps[] = +{ + {"activate", 1, ActivateOp, 3, 0, "?pattern?...",}, + {"bind", 1, BindOp, 3, 6, "elem sequence command",}, + {"cget", 2, CgetOp, 4, 4, "option",}, + {"configure", 2, ConfigureOp, 3, 0, "?option value?...",}, + {"curselection", 2, CurselectionOp, 3, 3, "",}, + {"deactivate", 1, ActivateOp, 3, 0, "?pattern?...",}, + {"focus", 1, FocusOp, 4, 4, "elem",}, + {"get", 1, GetOp, 4, 4, "elem",}, + {"icon", 1, IconOp, 5, 5, "elem image",}, + {"selection", 1, SelectionOp, 3, 0, "args"}, +}; +static int nLegendOps = sizeof(legendOps) / sizeof(Blt_OpSpec); + +int +Blt_LegendOp(Graph *graphPtr, Tcl_Interp *interp, int objc, + Tcl_Obj *const *objv) +{ + GraphLegendProc *proc; + + proc = Blt_GetOpFromObj(interp, nLegendOps, legendOps, BLT_OP_ARG2, + objc, objv,0); + if (proc == NULL) { + return TCL_ERROR; + } + return (*proc) (graphPtr, interp, objc, objv); +} + +int +Blt_Legend_Site(Graph *graphPtr) +{ + return graphPtr->legend->site; +} + +int +Blt_Legend_Width(Graph *graphPtr) +{ + return graphPtr->legend->width; +} + +int +Blt_Legend_Height(Graph *graphPtr) +{ + return graphPtr->legend->height; +} + +int +Blt_Legend_IsHidden(Graph *graphPtr) +{ + return (graphPtr->legend->flags & HIDE); +} + +int +Blt_Legend_IsRaised(Graph *graphPtr) +{ + return (graphPtr->legend->flags & RAISED); +} + +int +Blt_Legend_X(Graph *graphPtr) +{ + return graphPtr->legend->x; +} + +int +Blt_Legend_Y(Graph *graphPtr) +{ + return graphPtr->legend->y; +} + +void +Blt_Legend_RemoveElement(Graph *graphPtr, Element *elemPtr) +{ + Blt_DeleteBindings(graphPtr->legend->bindTable, elemPtr); +} + +/* + *--------------------------------------------------------------------------- + * + * SelectionProc -- + * + * This procedure is called back by Tk when the selection is requested by + * someone. It returns part or all of the selection in a buffer provided + * by the caller. + * + * Results: + * The return value is the number of non-NULL bytes stored at buffer. + * Buffer is filled (or partially filled) with a NUL-terminated string + * containing part or all of the selection, as given by offset and + * maxBytes. + * + * Side effects: + * None. + * + *--------------------------------------------------------------------------- + */ +static int +SelectionProc( + ClientData clientData, /* Information about the widget. */ + int offset, /* Offset within selection of first + * character to be returned. */ + char *buffer, /* Location in which to place + * selection. */ + int maxBytes) /* Maximum number of bytes to place at + * buffer, not including terminating + * NULL character. */ +{ + Legend *legendPtr = clientData; + int nBytes; + Tcl_DString dString; + + if ((legendPtr->flags & SELECT_EXPORT) == 0) { + return -1; + } + /* Retrieve the names of the selected entries. */ + Tcl_DStringInit(&dString); + if (legendPtr->flags & SELECT_SORTED) { + Blt_ChainLink link; + + for (link = Blt_Chain_FirstLink(legendPtr->selected); + link != NULL; link = Blt_Chain_NextLink(link)) { + Element *elemPtr; + + elemPtr = Blt_Chain_GetValue(link); + Tcl_DStringAppend(&dString, elemPtr->obj.name, -1); + Tcl_DStringAppend(&dString, "\n", -1); + } + } else { + Blt_ChainLink link; + Graph *graphPtr; + + graphPtr = legendPtr->graphPtr; + /* List of selected entries is in stacking order. */ + for (link = Blt_Chain_FirstLink(graphPtr->elements.displayList); + link != NULL; link = Blt_Chain_NextLink(link)) { + Element *elemPtr; + + elemPtr = Blt_Chain_GetValue(link); + if (EntryIsSelected(legendPtr, elemPtr)) { + Tcl_DStringAppend(&dString, elemPtr->obj.name, -1); + Tcl_DStringAppend(&dString, "\n", -1); + } + } + } + nBytes = Tcl_DStringLength(&dString) - offset; + strncpy(buffer, Tcl_DStringValue(&dString) + offset, maxBytes); + Tcl_DStringFree(&dString); + buffer[maxBytes] = '\0'; + return MIN(nBytes, maxBytes); +} + + +/* + *--------------------------------------------------------------------------- + * + * BlinkCursorProc -- + * + * This procedure is called as a timer handler to blink the insertion + * cursor off and on. + * + * Results: + * None. + * + * Side effects: + * The cursor gets turned on or off, redisplay gets invoked, and this + * procedure reschedules itself. + * + *--------------------------------------------------------------------------- + */ +static void +BlinkCursorProc(ClientData clientData) +{ + Graph *graphPtr = clientData; + Legend *legendPtr; + + legendPtr = graphPtr->legend; + if (!(legendPtr->flags & FOCUS) || (legendPtr->offTime == 0)) { + return; + } + if (legendPtr->active) { + int time; + + legendPtr->cursorOn ^= 1; + time = (legendPtr->cursorOn) ? legendPtr->onTime : legendPtr->offTime; + legendPtr->timerToken = Tcl_CreateTimerHandler(time, BlinkCursorProc, + graphPtr); + Blt_Legend_EventuallyRedraw(graphPtr); + } +} diff --git a/src/bltGrLegd.h b/src/bltGrLegd.h new file mode 100644 index 0000000..0ba1cb3 --- /dev/null +++ b/src/bltGrLegd.h @@ -0,0 +1,60 @@ +/* + * bltGrLegd.h -- + * + * Copyright 1993-2004 George A Howlett. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY + * KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS + * OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef _BLT_GR_LEGEND_H +#define _BLT_GR_LEGEND_H + +#define LEGEND_RIGHT (1<<0) /* Right margin */ +#define LEGEND_LEFT (1<<1) /* Left margin */ +#define LEGEND_BOTTOM (1<<2) /* Bottom margin */ +#define LEGEND_TOP (1<<3) /* Top margin, below the graph title. */ +#define LEGEND_PLOT (1<<4) /* Plot area */ +#define LEGEND_XY (1<<5) /* Screen coordinates in the plotting + * area. */ +#define LEGEND_WINDOW (1<<6) /* External window. */ +#define LEGEND_MARGIN_MASK \ + (LEGEND_RIGHT | LEGEND_LEFT | LEGEND_BOTTOM | LEGEND_TOP) +#define LEGEND_PLOTAREA_MASK (LEGEND_PLOT | LEGEND_XY) + +BLT_EXTERN int Blt_CreateLegend(Graph *graphPtr); +BLT_EXTERN void Blt_DestroyLegend(Graph *graphPtr); +BLT_EXTERN void Blt_DrawLegend(Graph *graphPtr, Drawable drawable); +BLT_EXTERN void Blt_MapLegend(Graph *graphPtr, int width, int height); +BLT_EXTERN int Blt_LegendOp(Graph *graphPtr, Tcl_Interp *interp, int objc, + Tcl_Obj *const *objv); +BLT_EXTERN int Blt_Legend_Site(Graph *graphPtr); +BLT_EXTERN int Blt_Legend_Width(Graph *graphPtr); +BLT_EXTERN int Blt_Legend_Height(Graph *graphPtr); +BLT_EXTERN int Blt_Legend_IsHidden(Graph *graphPtr); +BLT_EXTERN int Blt_Legend_IsRaised(Graph *graphPtr); +BLT_EXTERN int Blt_Legend_X(Graph *graphPtr); +BLT_EXTERN int Blt_Legend_Y(Graph *graphPtr); +BLT_EXTERN void Blt_Legend_RemoveElement(Graph *graphPtr, Element *elemPtr); +BLT_EXTERN void Blt_Legend_EventuallyRedraw(Graph *graphPtr); + +#endif /* BLT_GR_LEGEND_H */ diff --git a/src/bltGrMarker.C b/src/bltGrMarker.C new file mode 100644 index 0000000..c9546f6 --- /dev/null +++ b/src/bltGrMarker.C @@ -0,0 +1,5196 @@ + +/* + * bltGrMarker.c -- + * + * This module implements markers for the BLT graph widget. + * + * Copyright 1993-2004 George A Howlett. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include "bltGraph.h" +#include "bltOp.h" +#include "bltImage.h" +#include "bltPicture.h" +#include "bltPainter.h" +#include "bltChain.h" +#include "bltGrElem.h" +#include "bltBitmap.h" + +typedef int (GraphMarkerProc)(Graph *graphPtr, Tcl_Interp *interp, int objc, + Tcl_Obj *const *objv); + +#define GETBITMAP(b) \ + (((b)->destBitmap == None) ? (b)->srcBitmap : (b)->destBitmap) + +#define MAX_OUTLINE_POINTS 12 + +#define IMAGE_PHOTO (1<<7) + +/* Map graph coordinates to normalized coordinates [0..1] */ +#define NORMALIZE(A,x) (((x) - (A)->axisRange.min) * (A)->axisRange.scale) + +#define DEF_MARKER_ANCHOR "center" +#define DEF_MARKER_BACKGROUND RGB_WHITE +#define DEF_MARKER_BITMAP (char *)NULL +#define DEF_MARKER_CAP_STYLE "butt" +#define DEF_MARKER_COORDS (char *)NULL +#define DEF_MARKER_DASHES (char *)NULL +#define DEF_MARKER_DASH_OFFSET "0" +#define DEF_MARKER_ELEMENT (char *)NULL +#define DEF_MARKER_FOREGROUND RGB_BLACK +#define DEF_MARKER_FILL_COLOR RGB_RED +#define DEF_MARKER_FONT STD_FONT +#define DEF_MARKER_GAP_COLOR RGB_PINK +#define DEF_MARKER_HEIGHT "0" +#define DEF_MARKER_HIDE "no" +#define DEF_MARKER_JOIN_STYLE "miter" +#define DEF_MARKER_JUSTIFY "left" +#define DEF_MARKER_LINE_WIDTH "1" +#define DEF_MARKER_MAP_X "x" +#define DEF_MARKER_MAP_Y "y" +#define DEF_MARKER_NAME (char *)NULL +#define DEF_MARKER_OUTLINE_COLOR RGB_BLACK +#define DEF_MARKER_PAD "4" +#define DEF_MARKER_ANGLE "0.0" +#define DEF_MARKER_SCALE "1.0" +#define DEF_MARKER_STATE "normal" +#define DEF_MARKER_STIPPLE (char *)NULL +#define DEF_MARKER_TEXT (char *)NULL +#define DEF_MARKER_UNDER "no" +#define DEF_MARKER_WIDTH "0" +#define DEF_MARKER_WINDOW (char *)NULL +#define DEF_MARKER_XOR "no" +#define DEF_MARKER_X_OFFSET "0" +#define DEF_MARKER_Y_OFFSET "0" +#define DEF_MARKER_FILTER "box" + +#define DEF_TEXT_TAGS "Text all" +#define DEF_IMAGE_TAGS "Image all" +#define DEF_BITMAP_TAGS "Bitmap all" +#define DEF_WINDOW_TAGS "Window all" +#define DEF_POLYGON_TAGS "Polygon all" +#define DEF_LINE_TAGS "Line all" + +static Blt_OptionParseProc ObjToCoordsProc; +static Blt_OptionPrintProc CoordsToObjProc; +static Blt_OptionFreeProc FreeCoordsProc; +static Blt_CustomOption coordsOption = +{ + ObjToCoordsProc, CoordsToObjProc, FreeCoordsProc, (ClientData)0 +}; +static Blt_OptionFreeProc FreeColorPairProc; +static Blt_OptionParseProc ObjToColorPairProc; +static Blt_OptionPrintProc ColorPairToObjProc; +static Blt_CustomOption colorPairOption = +{ + ObjToColorPairProc, ColorPairToObjProc, FreeColorPairProc, (ClientData)0 +}; + +static Blt_OptionParseProc ObjToPictImageProc; +static Blt_OptionPrintProc PictImageToObjProc; +static Blt_OptionFreeProc FreePictImageProc; +static Blt_CustomOption pictImageOption = +{ + ObjToPictImageProc, PictImageToObjProc, FreePictImageProc, (ClientData)0 +}; + +BLT_EXTERN Blt_CustomOption bltXAxisOption; +BLT_EXTERN Blt_CustomOption bltYAxisOption; +BLT_EXTERN Blt_CustomOption bltFilterOption; + +typedef Marker *(MarkerCreateProc)(void); +typedef void (MarkerDrawProc)(Marker *markerPtr, Drawable drawable); +typedef void (MarkerFreeProc)(Marker *markerPtr); +typedef int (MarkerConfigProc)(Marker *markerPtr); +typedef void (MarkerMapProc)(Marker *markerPtr); +typedef void (MarkerPostscriptProc)(Marker *markerPtr, Blt_Ps ps); +typedef int (MarkerPointProc)(Marker *markerPtr, Point2d *samplePtr); +typedef int (MarkerRegionProc)(Marker *markerPtr, Region2d *extsPtr, + int enclosed); + +static Tcl_FreeProc FreeMarker; + +typedef struct { + Blt_ConfigSpec *configSpecs; /* Marker configuration + * specifications */ + MarkerConfigProc *configProc; + MarkerDrawProc *drawProc; + MarkerFreeProc *freeProc; + MarkerMapProc *mapProc; + MarkerPointProc *pointProc; + MarkerRegionProc *regionProc; + MarkerPostscriptProc *postscriptProc; + +} MarkerClass; + +/* + *--------------------------------------------------------------------------- + * + * Marker -- + * + * Structure defining the generic marker. In C++ parlance this would be + * the base class from which all markers are derived. + * + * This structure corresponds with the specific types of markers. Don't + * change this structure without changing the individual marker + * structures of each type below. + * + * -------------------------------------------------------------------------- + */ +struct _Marker { + GraphObj obj; /* Must be first field in marker. */ + + MarkerClass *classPtr; + + Blt_HashEntry *hashPtr; + + Blt_ChainLink link; + + const char *elemName; /* Element associated with marker. Let's + * you link a marker to an element. The + * marker is drawn only if the element + * is also visible. */ + Axis2d axes; + Point2d *worldPts; /* Coordinate array to position + * marker */ + int nWorldPts; /* Number of points in above array */ + int drawUnder; /* If non-zero, draw the marker + * underneath any elements. This can be + * a performance penalty because the + * graph must be redraw entirely each + * time the marker is redrawn. */ + + int clipped; /* Indicates if the marker is totally + * clipped by the plotting area. */ + + unsigned int flags; + + + int xOffset, yOffset; /* Pixel offset from graph position */ + + int state; +}; +/* + *--------------------------------------------------------------------------- + * + * BitmapMarker -- + * + *--------------------------------------------------------------------------- + */ +typedef struct { + GraphObj obj; /* Must be first field in marker. */ + + MarkerClass *classPtr; + + Blt_HashEntry *hashPtr; + + Blt_ChainLink link; + + const char *elemName; /* Element associated with marker. Let's + * you link a marker to an element. The + * marker is drawn only if the element + * is also visible. */ + Axis2d axes; + + Point2d *worldPts; /* Coordinate array to position + * marker. */ + int nWorldPts; /* # of points in above array. */ + + int drawUnder; /* If non-zero, draw the marker + * underneath any elements. This can be + * a performance penalty because the + * graph must be redraw entirely each + * time the marker is redrawn. */ + + int clipped; /* Indicates if the marker is totally + * clipped by the plotting area. */ + + unsigned int flags; + + + int xOffset, yOffset; /* Pixel offset from graph position */ + + int state; + + /* Fields specific to bitmap markers. */ + + Pixmap srcBitmap; /* Original bitmap. May be further + * scaled or rotated. */ + float reqAngle; /* Requested rotation of the bitmap */ + float angle; /* Normalized rotation (0..360 + * degrees) */ + Tk_Anchor anchor; /* If only one X-Y coordinate is given, + * indicates how to translate the given + * marker position. Otherwise, if there + * are two X-Y coordinates, then this + * value is ignored. */ + Point2d anchorPt; /* Translated anchor point. */ + + XColor *outlineColor; /* Foreground color */ + XColor *fillColor; /* Background color */ + + GC gc; /* Private graphic context */ + GC fillGC; /* Shared graphic context */ + Pixmap destBitmap; /* Bitmap to be drawn. */ + int destWidth, destHeight; /* Dimensions of the final bitmap */ + + Point2d outline[MAX_OUTLINE_POINTS];/* Polygon representing the background + * of the bitmap. */ + int nOutlinePts; +} BitmapMarker; + +static Blt_ConfigSpec bitmapConfigSpecs[] = +{ + {BLT_CONFIG_ANCHOR, "-anchor", "anchor", "Anchor", DEF_MARKER_ANCHOR, + Blt_Offset(BitmapMarker, anchor), 0}, + {BLT_CONFIG_COLOR, "-background", "background", "Background", + DEF_MARKER_BACKGROUND, Blt_Offset(BitmapMarker, fillColor), + BLT_CONFIG_NULL_OK}, + {BLT_CONFIG_SYNONYM, "-bg", "background", (char *)NULL, (char *)NULL, 0, 0}, + {BLT_CONFIG_LIST, "-bindtags", "bindTags", "BindTags", DEF_BITMAP_TAGS, + Blt_Offset(BitmapMarker, obj.tags), BLT_CONFIG_NULL_OK}, + {BLT_CONFIG_BITMAP, "-bitmap", "bitmap", "Bitmap", DEF_MARKER_BITMAP, + Blt_Offset(BitmapMarker, srcBitmap), BLT_CONFIG_NULL_OK}, + {BLT_CONFIG_CUSTOM, "-coords", "coords", "Coords", DEF_MARKER_COORDS, + Blt_Offset(BitmapMarker, worldPts), BLT_CONFIG_NULL_OK, + &coordsOption}, + {BLT_CONFIG_STRING, "-element", "element", "Element", DEF_MARKER_ELEMENT, + Blt_Offset(BitmapMarker, elemName), BLT_CONFIG_NULL_OK}, + {BLT_CONFIG_SYNONYM, "-fg", "foreground", (char *)NULL, (char *)NULL, 0, 0}, + {BLT_CONFIG_SYNONYM, "-fill", "background", (char *)NULL, (char *)NULL, + 0, 0}, + {BLT_CONFIG_COLOR, "-foreground", "foreground", "Foreground", + DEF_MARKER_FOREGROUND, Blt_Offset(BitmapMarker, outlineColor), + BLT_CONFIG_NULL_OK}, + {BLT_CONFIG_BITMASK, "-hide", "hide", "Hide", DEF_MARKER_HIDE, + Blt_Offset(BitmapMarker, flags), BLT_CONFIG_DONT_SET_DEFAULT, + (Blt_CustomOption *)HIDE}, + {BLT_CONFIG_CUSTOM, "-mapx", "mapX", "MapX", DEF_MARKER_MAP_X, + Blt_Offset(BitmapMarker, axes.x), 0, &bltXAxisOption}, + {BLT_CONFIG_CUSTOM, "-mapy", "mapY", "MapY", DEF_MARKER_MAP_Y, + Blt_Offset(BitmapMarker, axes.y), 0, &bltYAxisOption}, + {BLT_CONFIG_STRING, "-name", (char *)NULL, (char *)NULL, DEF_MARKER_NAME, + Blt_Offset(BitmapMarker, obj.name), BLT_CONFIG_NULL_OK}, + {BLT_CONFIG_SYNONYM, "-outline", "foreground", (char *)NULL, (char *)NULL, + 0, 0}, + {BLT_CONFIG_FLOAT, "-rotate", "rotate", "Rotate", DEF_MARKER_ANGLE, + Blt_Offset(BitmapMarker, reqAngle), BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_STATE, "-state", "state", "State", DEF_MARKER_STATE, + Blt_Offset(BitmapMarker, state), BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_BOOLEAN, "-under", "under", "Under", DEF_MARKER_UNDER, + Blt_Offset(BitmapMarker, drawUnder), BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_PIXELS, "-xoffset", "xOffset", "XOffset", DEF_MARKER_X_OFFSET, + Blt_Offset(BitmapMarker, xOffset), BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_PIXELS, "-yoffset", "yOffset", "YOffset", DEF_MARKER_Y_OFFSET, + Blt_Offset(BitmapMarker, yOffset), BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_END, NULL, NULL, NULL, NULL, 0, 0} +}; + +static MarkerConfigProc ConfigureBitmapProc; +static MarkerCreateProc CreateBitmapProc; +static MarkerDrawProc DrawBitmapProc; +static MarkerFreeProc FreeBitmapProc; +static MarkerMapProc MapBitmapProc; +static MarkerPointProc PointInBitmapProc; +static MarkerPostscriptProc BitmapToPostscriptProc; +static MarkerRegionProc RegionInBitmapProc; + +static MarkerClass bitmapMarkerClass = { + bitmapConfigSpecs, + ConfigureBitmapProc, + DrawBitmapProc, + FreeBitmapProc, + MapBitmapProc, + PointInBitmapProc, + RegionInBitmapProc, + BitmapToPostscriptProc, +}; + +/* + *--------------------------------------------------------------------------- + * + * ImageMarker -- + * + *--------------------------------------------------------------------------- + */ +typedef struct { + GraphObj obj; /* Must be first field in marker. */ + + MarkerClass *classPtr; + + Blt_HashEntry *hashPtr; + + Blt_ChainLink link; + + const char *elemName; /* Element associated with marker. Let's + * you link a marker to an element. The + * marker is drawn only if the element + * is also visible. */ + Axis2d axes; + + Point2d *worldPts; /* Coordinate array to position + * marker. */ + + int nWorldPts; /* # of points in above array. */ + + int drawUnder; /* If non-zero, draw the marker + * underneath any elements. This can be + * a performance penalty because the + * graph must be redraw entirely each + * time the marker is redrawn. */ + + int clipped; /* Indicates if the marker is totally + * clipped by the plotting area. */ + + unsigned int flags; + + + int xOffset, yOffset; /* Pixel offset from graph position */ + + int state; + + Tk_Image tkImage; /* Tk image to be displayed. */ + Tk_Anchor anchor; /* Indicates how to translate the given + * marker position. */ + Point2d anchorPt; /* Translated anchor point. */ + int width, height; /* Dimensions of the possibly scaled + * image. */ + Blt_Painter painter; + Blt_Picture picture; + Blt_ResampleFilter filter; + int pictX, pictY; /* */ + Blt_Picture scaled; /* Pixmap containing the scaled image */ + GC gc; + +} ImageMarker; + +static Blt_ConfigSpec imageConfigSpecs[] = +{ + {BLT_CONFIG_ANCHOR, "-anchor", "anchor", "Anchor", DEF_MARKER_ANCHOR, + Blt_Offset(ImageMarker, anchor), 0}, + {BLT_CONFIG_LIST, "-bindtags", "bindTags", "BindTags", DEF_IMAGE_TAGS, + Blt_Offset(ImageMarker, obj.tags), BLT_CONFIG_NULL_OK}, + {BLT_CONFIG_CUSTOM, "-coords", "coords", "Coords", DEF_MARKER_COORDS, + Blt_Offset(ImageMarker, worldPts), BLT_CONFIG_NULL_OK, &coordsOption}, + {BLT_CONFIG_STRING, "-element", "element", "Element", DEF_MARKER_ELEMENT, + Blt_Offset(ImageMarker, elemName), BLT_CONFIG_NULL_OK}, + {BLT_CONFIG_BITMASK, "-hide", "hide", "Hide", DEF_MARKER_HIDE, + Blt_Offset(ImageMarker, flags), BLT_CONFIG_DONT_SET_DEFAULT, + (Blt_CustomOption *)HIDE}, + {BLT_CONFIG_CUSTOM, "-image", "image", "Image", (char *)NULL, + Blt_Offset(ImageMarker, picture), BLT_CONFIG_NULL_OK, &pictImageOption}, + {BLT_CONFIG_CUSTOM, "-mapx", "mapX", "MapX", DEF_MARKER_MAP_X, + Blt_Offset(ImageMarker, axes.x), 0, &bltXAxisOption}, + {BLT_CONFIG_CUSTOM, "-mapy", "mapY", "MapY", DEF_MARKER_MAP_Y, + Blt_Offset(ImageMarker, axes.y), 0, &bltYAxisOption}, + {BLT_CONFIG_STRING, "-name", (char *)NULL, (char *)NULL, DEF_MARKER_NAME, + Blt_Offset(ImageMarker, obj.name), BLT_CONFIG_NULL_OK}, + {BLT_CONFIG_CUSTOM, "-resamplefilter", "resampleFilter", "ResampleFilter", + DEF_MARKER_FILTER, Blt_Offset(ImageMarker, filter), + BLT_CONFIG_NULL_OK | BLT_CONFIG_DONT_SET_DEFAULT, &bltFilterOption}, + {BLT_CONFIG_STATE, "-state", "state", "State", DEF_MARKER_STATE, + Blt_Offset(ImageMarker, state), BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_BOOLEAN, "-under", "under", "Under", DEF_MARKER_UNDER, + Blt_Offset(ImageMarker, drawUnder), BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_PIXELS, "-xoffset", "xOffset", "XOffset", DEF_MARKER_X_OFFSET, + Blt_Offset(ImageMarker, xOffset), BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_PIXELS, "-yoffset", "yOffset", "YOffset", DEF_MARKER_Y_OFFSET, + Blt_Offset(ImageMarker, yOffset), BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_END, NULL, NULL, NULL, NULL, 0, 0} +}; + +static MarkerConfigProc ConfigureImageProc; +static MarkerCreateProc CreateImageProc; +static MarkerDrawProc DrawImageProc; +static MarkerFreeProc FreeImageProc; +static MarkerMapProc MapImageProc; +static MarkerPointProc PointInImageProc; +static MarkerPostscriptProc ImageToPostscriptProc; +static MarkerRegionProc RegionInImageProc; + +static MarkerClass imageMarkerClass = { + imageConfigSpecs, + ConfigureImageProc, + DrawImageProc, + FreeImageProc, + MapImageProc, + PointInImageProc, + RegionInImageProc, + ImageToPostscriptProc, +}; + +/* + *--------------------------------------------------------------------------- + * + * LineMarker -- + * + *--------------------------------------------------------------------------- + */ +typedef struct { + GraphObj obj; /* Must be first field in marker. */ + + MarkerClass *classPtr; + + Blt_HashEntry *hashPtr; + + Blt_ChainLink link; + + const char *elemName; /* Element associated with marker. Let's + * you link a marker to an element. The + * marker is drawn only if the element + * is also visible. */ + Axis2d axes; + + Point2d *worldPts; /* Coordinate array to position + * marker. */ + + int nWorldPts; /* Number of points in above array */ + + int drawUnder; /* If non-zero, draw the marker + * underneath any elements. This can be + * a performance penalty because the + * graph must be redraw entirely each + * time the marker is redrawn. */ + + int clipped; /* Indicates if the marker is totally + * clipped by the plotting area. */ + + unsigned int flags; + + + int xOffset, yOffset; /* Pixel offset from graph position */ + + int state; + + XColor *fillColor; + XColor *outlineColor; /* Foreground and background colors */ + + int lineWidth; /* Line width. */ + int capStyle; /* Cap style. */ + int joinStyle; /* Join style.*/ + Blt_Dashes dashes; /* Dash list values (max 11) */ + + GC gc; /* Private graphic context */ + + Segment2d *segments; /* Malloc'ed array of points. + * Represents individual line segments + * (2 points per segment) comprising the + * mapped line. The segments may not + * necessarily be connected after + * clipping. */ + int nSegments; /* # segments in the above array. */ + int xor; + int xorState; /* State of the XOR drawing. Indicates + * if the marker is currently drawn. */ +} LineMarker; + +static Blt_ConfigSpec lineConfigSpecs[] = +{ + {BLT_CONFIG_LIST, "-bindtags", "bindTags", "BindTags", DEF_LINE_TAGS, + Blt_Offset(LineMarker, obj.tags), BLT_CONFIG_NULL_OK}, + {BLT_CONFIG_CAP_STYLE, "-cap", "cap", "Cap", DEF_MARKER_CAP_STYLE, + Blt_Offset(LineMarker, capStyle), BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_CUSTOM, "-coords", "coords", "Coords", DEF_MARKER_COORDS, + Blt_Offset(LineMarker, worldPts), BLT_CONFIG_NULL_OK, &coordsOption}, + {BLT_CONFIG_DASHES, "-dashes", "dashes", "Dashes", DEF_MARKER_DASHES, + Blt_Offset(LineMarker, dashes), BLT_CONFIG_NULL_OK}, + {BLT_CONFIG_PIXELS_NNEG, "-dashoffset", "dashOffset", "DashOffset", + DEF_MARKER_DASH_OFFSET, Blt_Offset(LineMarker, dashes.offset), + BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_STRING, "-element", "element", "Element", DEF_MARKER_ELEMENT, + Blt_Offset(LineMarker, elemName), BLT_CONFIG_NULL_OK}, + {BLT_CONFIG_COLOR, "-fill", "fill", "Fill", (char *)NULL, + Blt_Offset(LineMarker, fillColor), BLT_CONFIG_NULL_OK}, + {BLT_CONFIG_JOIN_STYLE, "-join", "join", "Join", DEF_MARKER_JOIN_STYLE, + Blt_Offset(LineMarker, joinStyle), BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_PIXELS_NNEG, "-linewidth", "lineWidth", "LineWidth", + DEF_MARKER_LINE_WIDTH, Blt_Offset(LineMarker, lineWidth), + BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_BITMASK, "-hide", "hide", "Hide", DEF_MARKER_HIDE, + Blt_Offset(LineMarker, flags), BLT_CONFIG_DONT_SET_DEFAULT, + (Blt_CustomOption *)HIDE}, + {BLT_CONFIG_CUSTOM, "-mapx", "mapX", "MapX", DEF_MARKER_MAP_X, + Blt_Offset(LineMarker, axes.x), 0, &bltXAxisOption}, + {BLT_CONFIG_CUSTOM, "-mapy", "mapY", "MapY", DEF_MARKER_MAP_Y, + Blt_Offset(LineMarker, axes.y), 0, &bltYAxisOption}, + {BLT_CONFIG_STRING, "-name", (char *)NULL, (char *)NULL, DEF_MARKER_NAME, + Blt_Offset(LineMarker, obj.name), BLT_CONFIG_NULL_OK}, + {BLT_CONFIG_COLOR, "-outline", "outline", "Outline", + DEF_MARKER_OUTLINE_COLOR, Blt_Offset(LineMarker, outlineColor), + BLT_CONFIG_NULL_OK}, + {BLT_CONFIG_STATE, "-state", "state", "State", DEF_MARKER_STATE, + Blt_Offset(LineMarker, state), BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_BOOLEAN, "-under", "under", "Under", DEF_MARKER_UNDER, + Blt_Offset(LineMarker, drawUnder), BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_PIXELS, "-xoffset", "xOffset", "XOffset", DEF_MARKER_X_OFFSET, + Blt_Offset(LineMarker, xOffset), BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_BOOLEAN, "-xor", "xor", "Xor", DEF_MARKER_XOR, + Blt_Offset(LineMarker, xor), BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_PIXELS, "-yoffset", "yOffset", "YOffset", DEF_MARKER_Y_OFFSET, + Blt_Offset(LineMarker, yOffset), BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_END, NULL, NULL, NULL, NULL, 0, 0} +}; + +static MarkerConfigProc ConfigureLineProc; +static MarkerCreateProc CreateLineProc; +static MarkerDrawProc DrawLineProc; +static MarkerFreeProc FreeLineProc; +static MarkerMapProc MapLineProc; +static MarkerPointProc PointInLineProc; +static MarkerPostscriptProc LineToPostscriptProc; +static MarkerRegionProc RegionInLineProc; + +static MarkerClass lineMarkerClass = { + lineConfigSpecs, + ConfigureLineProc, + DrawLineProc, + FreeLineProc, + MapLineProc, + PointInLineProc, + RegionInLineProc, + LineToPostscriptProc, +}; + +/* + *--------------------------------------------------------------------------- + * + * PolygonMarker -- + * + *--------------------------------------------------------------------------- + */ +typedef struct { + GraphObj obj; /* Must be first field in marker. */ + + MarkerClass *classPtr; + + Blt_HashEntry *hashPtr; + + Blt_ChainLink link; + + const char *elemName; /* Element associated with marker. Let's + * you link a marker to an element. The + * marker is drawn only if the element + * is also visible. */ + Axis2d axes; + + Point2d *worldPts; /* Coordinate array to position + * marker. */ + + int nWorldPts; /* Number of points in above array */ + + int drawUnder; /* If non-zero, draw the marker + * underneath any elements. This can be + * a performance penalty because the + * graph must be redraw entirely each + * time the marker is redrawn. */ + + int clipped; /* Indicates if the marker is totally + * clipped by the plotting area. */ + + unsigned int flags; + + + int xOffset, yOffset; /* Pixel offset from graph position */ + + int state; + + Point2d *screenPts; /* Array of points representing the + * polygon in screen coordinates. It's + * not used for drawing, but to generate + * the outlinePts and fillPts arrays + * that are the coordinates of the + * possibly clipped outline and filled + * polygon. */ + + ColorPair outline; + ColorPair fill; + + Pixmap stipple; /* Stipple pattern to fill the + * polygon. */ + int lineWidth; /* Width of polygon outline. */ + int capStyle; + int joinStyle; + Blt_Dashes dashes; /* List of dash values. Indicates how + * to draw the dashed line. If no dash + * values are provided, or the first + * value is zero, then the line is drawn + * solid. */ + + GC outlineGC; /* Graphics context to draw the outline + * of the polygon. */ + GC fillGC; /* Graphics context to draw the filled + * polygon. */ + + Point2d *fillPts; /* Malloc'ed array of points used to + * draw the filled polygon. These points + * may form a degenerate polygon after + * clipping. */ + int nFillPts; /* # points in the above array. */ + Segment2d *outlinePts; /* Malloc'ed array of points. + * Represents individual line segments + * (2 points per segment) comprising the + * outline of the polygon. The segments + * may not necessarily be closed or + * connected after clipping. */ + int nOutlinePts; /* # points in the above array. */ + int xor; + int xorState; /* State of the XOR drawing. Indicates + * if the marker is visible. We have to + * drawn it again to erase it. */ +} PolygonMarker; + +static Blt_ConfigSpec polygonConfigSpecs[] = +{ + {BLT_CONFIG_LIST, "-bindtags", "bindTags", "BindTags", DEF_POLYGON_TAGS, + Blt_Offset(PolygonMarker, obj.tags), BLT_CONFIG_NULL_OK}, + {BLT_CONFIG_CAP_STYLE, "-cap", "cap", "Cap", DEF_MARKER_CAP_STYLE, + Blt_Offset(PolygonMarker, capStyle), BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_CUSTOM, "-coords", "coords", "Coords", DEF_MARKER_COORDS, + Blt_Offset(PolygonMarker, worldPts), BLT_CONFIG_NULL_OK, &coordsOption}, + {BLT_CONFIG_DASHES, "-dashes", "dashes", "Dashes", DEF_MARKER_DASHES, + Blt_Offset(PolygonMarker, dashes), BLT_CONFIG_NULL_OK}, + {BLT_CONFIG_STRING, "-element", "element", "Element", DEF_MARKER_ELEMENT, + Blt_Offset(PolygonMarker, elemName), BLT_CONFIG_NULL_OK}, + {BLT_CONFIG_CUSTOM, "-fill", "fill", "Fill", DEF_MARKER_FILL_COLOR, + Blt_Offset(PolygonMarker, fill), BLT_CONFIG_NULL_OK, &colorPairOption}, + {BLT_CONFIG_JOIN_STYLE, "-join", "join", "Join", DEF_MARKER_JOIN_STYLE, + Blt_Offset(PolygonMarker, joinStyle), BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_PIXELS_NNEG, "-linewidth", "lineWidth", "LineWidth", + DEF_MARKER_LINE_WIDTH, Blt_Offset(PolygonMarker, lineWidth), + BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_BITMASK, "-hide", "hide", "Hide", DEF_MARKER_HIDE, + Blt_Offset(PolygonMarker, flags), BLT_CONFIG_DONT_SET_DEFAULT, + (Blt_CustomOption *)HIDE}, + {BLT_CONFIG_CUSTOM, "-mapx", "mapX", "MapX", DEF_MARKER_MAP_X, + Blt_Offset(PolygonMarker, axes.x), 0, &bltXAxisOption}, + {BLT_CONFIG_CUSTOM, "-mapy", "mapY", "MapY", DEF_MARKER_MAP_Y, + Blt_Offset(PolygonMarker, axes.y), 0, &bltYAxisOption}, + {BLT_CONFIG_STRING, "-name", (char *)NULL, (char *)NULL, DEF_MARKER_NAME, + Blt_Offset(PolygonMarker, obj.name), BLT_CONFIG_NULL_OK}, + {BLT_CONFIG_CUSTOM, "-outline", "outline", "Outline", + DEF_MARKER_OUTLINE_COLOR, Blt_Offset(PolygonMarker, outline), + BLT_CONFIG_NULL_OK, &colorPairOption}, + {BLT_CONFIG_STATE, "-state", "state", "State", DEF_MARKER_STATE, + Blt_Offset(PolygonMarker, state), BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_BITMAP, "-stipple", "stipple", "Stipple", DEF_MARKER_STIPPLE, + Blt_Offset(PolygonMarker, stipple), BLT_CONFIG_NULL_OK}, + {BLT_CONFIG_BOOLEAN, "-under", "under", "Under", DEF_MARKER_UNDER, + Blt_Offset(PolygonMarker, drawUnder), + BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_PIXELS, "-xoffset", "xOffset", "XOffset", DEF_MARKER_X_OFFSET, + Blt_Offset(PolygonMarker, xOffset), BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_BOOLEAN, "-xor", "xor", "Xor", DEF_MARKER_XOR, + Blt_Offset(PolygonMarker, xor), BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_PIXELS, "-yoffset", "yOffset", "YOffset", DEF_MARKER_Y_OFFSET, + Blt_Offset(PolygonMarker, yOffset), BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_END, NULL, NULL, NULL, NULL, 0, 0} +}; + +static MarkerConfigProc ConfigurePolygonProc; +static MarkerCreateProc CreatePolygonProc; +static MarkerDrawProc DrawPolygonProc; +static MarkerFreeProc FreePolygonProc; +static MarkerMapProc MapPolygonProc; +static MarkerPointProc PointInPolygonProc; +static MarkerPostscriptProc PolygonToPostscriptProc; +static MarkerRegionProc RegionInPolygonProc; + +static MarkerClass polygonMarkerClass = { + polygonConfigSpecs, + ConfigurePolygonProc, + DrawPolygonProc, + FreePolygonProc, + MapPolygonProc, + PointInPolygonProc, + RegionInPolygonProc, + PolygonToPostscriptProc, +}; + + +/* + *--------------------------------------------------------------------------- + * + * TextMarker -- + * + *--------------------------------------------------------------------------- + */ +typedef struct { + GraphObj obj; /* Must be first field in marker. */ + MarkerClass *classPtr; + Blt_HashEntry *hashPtr; + Blt_ChainLink link; + const char *elemName; /* Element associated with marker. Let's + * you link a marker to an element. The + * marker is drawn only if the element + * is also visible. */ + Axis2d axes; + Point2d *worldPts; /* Coordinate array to position + * marker. */ + int nWorldPts; /* # of points in above array */ + int drawUnder; /* If non-zero, draw the marker + * underneath any elements. This can be + * a performance penalty because the + * graph must be redraw entirely each + * time the marker is redrawn. */ + int clipped; /* Indicates if the marker is totally + * clipped by the plotting area. */ + unsigned int flags; + + + int xOffset, yOffset; /* Pixel offset from graph position */ + int state; + + /* Fields specific to text markers. */ +#ifdef notdef + const char *textVarName; /* Name of variable (malloc'ed) or + * NULL. If non-NULL, graph displays the + * contents of this variable. */ +#endif + const char *string; /* Text string to be display. The + * string make contain newlines. */ + Tk_Anchor anchor; /* Indicates how to translate the given + * marker position. */ + Point2d anchorPt; /* Translated anchor point. */ + int width, height; /* Dimension of bounding box. */ + TextStyle style; /* Text attributes (font, fg, anchor, + * etc) */ + Point2d outline[5]; + XColor *fillColor; + GC fillGC; +} TextMarker; + + +static Blt_ConfigSpec textConfigSpecs[] = +{ + {BLT_CONFIG_ANCHOR, "-anchor", "anchor", "Anchor", DEF_MARKER_ANCHOR, + Blt_Offset(TextMarker, anchor), 0}, + {BLT_CONFIG_COLOR, "-background", "background", "MarkerBackground", + (char *)NULL, Blt_Offset(TextMarker, fillColor), BLT_CONFIG_NULL_OK}, + {BLT_CONFIG_SYNONYM, "-bg", "background", "Background", (char *)NULL, 0, 0}, + {BLT_CONFIG_LIST, "-bindtags", "bindTags", "BindTags", DEF_TEXT_TAGS, + Blt_Offset(TextMarker, obj.tags), BLT_CONFIG_NULL_OK}, + {BLT_CONFIG_CUSTOM, "-coords", "coords", "Coords", DEF_MARKER_COORDS, + Blt_Offset(TextMarker, worldPts), BLT_CONFIG_NULL_OK, + &coordsOption}, + {BLT_CONFIG_STRING, "-element", "element", "Element", + DEF_MARKER_ELEMENT, Blt_Offset(TextMarker, elemName), + BLT_CONFIG_NULL_OK}, + {BLT_CONFIG_SYNONYM, "-fg", "foreground", "Foreground", (char *)NULL, 0, 0}, + {BLT_CONFIG_SYNONYM, "-fill", "background", (char *)NULL, (char *)NULL, + 0, 0}, + {BLT_CONFIG_FONT, "-font", "font", "Font", DEF_MARKER_FONT, + Blt_Offset(TextMarker, style.font), 0}, + {BLT_CONFIG_COLOR, "-foreground", "foreground", "Foreground", + DEF_MARKER_FOREGROUND, Blt_Offset(TextMarker, style.color), 0}, + {BLT_CONFIG_JUSTIFY, "-justify", "justify", "Justify", + DEF_MARKER_JUSTIFY, Blt_Offset(TextMarker, style.justify), + BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_BITMASK, "-hide", "hide", "Hide", DEF_MARKER_HIDE, + Blt_Offset(TextMarker, flags), BLT_CONFIG_DONT_SET_DEFAULT, + (Blt_CustomOption *)HIDE}, + {BLT_CONFIG_CUSTOM, "-mapx", "mapX", "MapX", DEF_MARKER_MAP_X, + Blt_Offset(TextMarker, axes.x), 0, &bltXAxisOption}, + {BLT_CONFIG_CUSTOM, "-mapy", "mapY", "MapY", DEF_MARKER_MAP_Y, + Blt_Offset(TextMarker, axes.y), 0, &bltYAxisOption}, + {BLT_CONFIG_STRING, "-name", (char *)NULL, (char *)NULL, DEF_MARKER_NAME, + Blt_Offset(TextMarker, obj.name), BLT_CONFIG_NULL_OK}, + {BLT_CONFIG_SYNONYM, "-outline", "foreground", (char *)NULL, (char *)NULL, + 0, 0}, + {BLT_CONFIG_PAD, "-padx", "padX", "PadX", DEF_MARKER_PAD, + Blt_Offset(TextMarker, style.xPad), BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_PAD, "-pady", "padY", "PadY", DEF_MARKER_PAD, + Blt_Offset(TextMarker, style.yPad), BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_FLOAT, "-rotate", "rotate", "Rotate", DEF_MARKER_ANGLE, + Blt_Offset(TextMarker, style.angle), BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_STATE, "-state", "state", "State", DEF_MARKER_STATE, + Blt_Offset(TextMarker, state), BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_STRING, "-text", "text", "Text", DEF_MARKER_TEXT, + Blt_Offset(TextMarker, string), BLT_CONFIG_NULL_OK}, + {BLT_CONFIG_BOOLEAN, "-under", "under", "Under", DEF_MARKER_UNDER, + Blt_Offset(TextMarker, drawUnder), BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_PIXELS, "-xoffset", "xOffset", "XOffset", DEF_MARKER_X_OFFSET, + Blt_Offset(TextMarker, xOffset), BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_PIXELS, "-yoffset", "yOffset", "YOffset", DEF_MARKER_Y_OFFSET, + Blt_Offset(TextMarker, yOffset), BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_END, NULL, NULL, NULL, NULL, 0, 0} +}; + +static MarkerConfigProc ConfigureTextProc; +static MarkerCreateProc CreateTextProc; +static MarkerDrawProc DrawTextProc; +static MarkerFreeProc FreeTextProc; +static MarkerMapProc MapTextProc; +static MarkerPointProc PointInTextProc; +static MarkerPostscriptProc TextToPostscriptProc; +static MarkerRegionProc RegionInTextProc; + +static MarkerClass textMarkerClass = { + textConfigSpecs, + ConfigureTextProc, + DrawTextProc, + FreeTextProc, + MapTextProc, + PointInTextProc, + RegionInTextProc, + TextToPostscriptProc, +}; + +/* + *--------------------------------------------------------------------------- + * + * WindowMarker -- + * + *--------------------------------------------------------------------------- + */ +typedef struct { + GraphObj obj; /* Must be first field in marker. */ + + MarkerClass *classPtr; + + Blt_HashEntry *hashPtr; + + Blt_ChainLink link; + + const char *elemName; /* Element associated with marker. Let's + * you link a marker to an element. The + * marker is drawn only if the element + * is also visible. */ + Axis2d axes; + + Point2d *worldPts; /* Coordinate array to position + * marker */ + + int nWorldPts; /* # of points in above array */ + + int drawUnder; /* If non-zero, draw the marker + * underneath any elements. This can be + * a performance penalty because the + * graph must be redraw entirely each + * time the marker is redrawn. */ + + int clipped; /* Indicates if the marker is totally + * clipped by the plotting area. */ + + unsigned int flags; + + + int xOffset, yOffset; /* Pixel offset from graph position */ + + int state; + + /* Fields specific to window markers. */ + + const char *childName; /* Name of child widget. */ + Tk_Window child; /* Window to display. */ + int reqWidth, reqHeight; /* If non-zero, this overrides the size + * requested by the child widget. */ + + Tk_Anchor anchor; /* Indicates how to translate the given + * marker position. */ + + Point2d anchorPt; /* Translated anchor point. */ + int width, height; /* Current size of the child window. */ + +} WindowMarker; + +static Blt_ConfigSpec windowConfigSpecs[] = +{ + {BLT_CONFIG_ANCHOR, "-anchor", "anchor", "Anchor", DEF_MARKER_ANCHOR, + Blt_Offset(WindowMarker, anchor), 0}, + {BLT_CONFIG_LIST, "-bindtags", "bindTags", "BindTags", DEF_WINDOW_TAGS, + Blt_Offset(WindowMarker, obj.tags), BLT_CONFIG_NULL_OK}, + {BLT_CONFIG_CUSTOM, "-coords", "coords", "Coords", DEF_MARKER_COORDS, + Blt_Offset(WindowMarker, worldPts), BLT_CONFIG_NULL_OK, + &coordsOption}, + {BLT_CONFIG_STRING, "-element", "element", "Element", DEF_MARKER_ELEMENT, + Blt_Offset(WindowMarker, elemName), BLT_CONFIG_NULL_OK}, + {BLT_CONFIG_PIXELS_POS, "-height", "height", "Height", DEF_MARKER_HEIGHT, + Blt_Offset(WindowMarker, reqHeight), BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_BITMASK, "-hide", "hide", "Hide", DEF_MARKER_HIDE, + Blt_Offset(WindowMarker, flags), BLT_CONFIG_DONT_SET_DEFAULT, + (Blt_CustomOption *)HIDE}, + {BLT_CONFIG_CUSTOM, "-mapx", "mapX", "MapX", DEF_MARKER_MAP_X, + Blt_Offset(WindowMarker, axes.x), 0, &bltXAxisOption}, + {BLT_CONFIG_CUSTOM, "-mapy", "mapY", "MapY", DEF_MARKER_MAP_Y, + Blt_Offset(WindowMarker, axes.y), 0, &bltYAxisOption}, + {BLT_CONFIG_STRING, "-name", (char *)NULL, (char *)NULL, DEF_MARKER_NAME, + Blt_Offset(WindowMarker, obj.name), BLT_CONFIG_NULL_OK}, + {BLT_CONFIG_STATE, "-state", "state", "State", DEF_MARKER_STATE, + Blt_Offset(WindowMarker, state), BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_BOOLEAN, "-under", "under", "Under", DEF_MARKER_UNDER, + Blt_Offset(WindowMarker, drawUnder), BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_PIXELS_POS, "-width", "width", "Width", DEF_MARKER_WIDTH, + Blt_Offset(WindowMarker, reqWidth), BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_STRING, "-window", "window", "Window", DEF_MARKER_WINDOW, + Blt_Offset(WindowMarker, childName), BLT_CONFIG_NULL_OK}, + {BLT_CONFIG_PIXELS, "-xoffset", "xOffset", "XOffset", DEF_MARKER_X_OFFSET, + Blt_Offset(WindowMarker, xOffset), BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_PIXELS, "-yoffset", "yOffset", "YOffset", DEF_MARKER_Y_OFFSET, + Blt_Offset(WindowMarker, yOffset), BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_END, NULL, NULL, NULL, NULL, 0, 0} +}; + +static MarkerConfigProc ConfigureWindowProc; +static MarkerCreateProc CreateWindowProc; +static MarkerDrawProc DrawWindowProc; +static MarkerFreeProc FreeWindowProc; +static MarkerMapProc MapWindowProc; +static MarkerPointProc PointInWindowProc; +static MarkerPostscriptProc WindowToPostscriptProc; +static MarkerRegionProc RegionInWindowProc; + +static MarkerClass windowMarkerClass = { + windowConfigSpecs, + ConfigureWindowProc, + DrawWindowProc, + FreeWindowProc, + MapWindowProc, + PointInWindowProc, + RegionInWindowProc, + WindowToPostscriptProc, +}; + +static Tk_ImageChangedProc ImageChangedProc; + + + +#ifdef notdef +static MarkerClass rectangleMarkerClass = { + rectangleConfigSpecs, + ConfigureRectangleProc, + DrawRectangleProc, + FreeRectangleProc, + MapRectangleProc, + PointInRectangleProc, + RegionInRectangleProc, + RectangleToPostscriptProc, +}; + +static MarkerClass ovalMarkerClass = { + ovalConfigSpecs, + ConfigureOvalProc, + DrawOvalProc, + FreeOvalProc, + MapOvalProc, + PointInOvalProc, + RegionInOvalProc, + OvalToPostscriptProc, +}; +#endif + +/* + *--------------------------------------------------------------------------- + * + * BoxesDontOverlap -- + * + * Tests if the bounding box of a marker overlaps the plotting area in + * any way. If so, the marker will be drawn. Just do a min/max test on + * the extents of both boxes. + * + * Note: It's assumed that the extents of the bounding box lie + * within the area. So for a 10x10 rectangle, bottom and + * left would be 9. + * + * Results: + * Returns 0 is the marker is visible in the plotting area, and 1 + * otherwise (marker is clipped). + * + *--------------------------------------------------------------------------- + */ +static int +BoxesDontOverlap(Graph *graphPtr, Region2d *extsPtr) +{ + assert(extsPtr->right >= extsPtr->left); + assert(extsPtr->bottom >= extsPtr->top); + assert(graphPtr->right >= graphPtr->left); + assert(graphPtr->bottom >= graphPtr->top); + + return (((double)graphPtr->right < extsPtr->left) || + ((double)graphPtr->bottom < extsPtr->top) || + (extsPtr->right < (double)graphPtr->left) || + (extsPtr->bottom < (double)graphPtr->top)); +} + + +/* + *--------------------------------------------------------------------------- + * + * GetCoordinate -- + * + * Convert the expression string into a floating point value. The * only + * reason we use this routine instead of Blt_ExprDouble is to * handle + * "elastic" bounds. That is, convert the strings "-Inf", * "Inf" into + * -(DBL_MAX) and DBL_MAX respectively. + * + * Results: + * The return value is a standard TCL result. The value of the + * expression is passed back via valuePtr. + * + *--------------------------------------------------------------------------- + */ +static int +GetCoordinate( + Tcl_Interp *interp, /* Interpreter to return results */ + Tcl_Obj *objPtr, /* Numeric expression string to + * parse */ + double *valuePtr) /* Real-valued result of expression */ +{ + char c; + const char *expr; + + expr = Tcl_GetString(objPtr); + c = expr[0]; + if ((c == 'I') && (strcmp(expr, "Inf") == 0)) { + *valuePtr = DBL_MAX; /* Elastic upper bound */ + } else if ((c == '-') && (expr[1] == 'I') && (strcmp(expr, "-Inf") == 0)) { + *valuePtr = -DBL_MAX; /* Elastic lower bound */ + } else if ((c == '+') && (expr[1] == 'I') && (strcmp(expr, "+Inf") == 0)) { + *valuePtr = DBL_MAX; /* Elastic upper bound */ + } else if (Blt_ExprDoubleFromObj(interp, objPtr, valuePtr) != TCL_OK) { + return TCL_ERROR; + } + return TCL_OK; +} + + +/* + *--------------------------------------------------------------------------- + * + * PrintCoordinate -- + * + * Convert the floating point value into its string representation. The + * only reason this routine is used in instead of sprintf, is to handle + * the "elastic" bounds. That is, convert the values DBL_MAX and + * -(DBL_MAX) into "+Inf" and "-Inf" respectively. + * + * Results: + * The return value is a standard TCL result. The string of the * + * expression is passed back via string. + * + * -------------------------------------------------------------------------- */ +static Tcl_Obj * +PrintCoordinate(double x) +{ + if (x == DBL_MAX) { + return Tcl_NewStringObj("+Inf", -1); + } else if (x == -DBL_MAX) { + return Tcl_NewStringObj("-Inf", -1); + } else { + return Tcl_NewDoubleObj(x); + } +} + +/* + *--------------------------------------------------------------------------- + * + * ParseCoordinates -- + * + * The TCL coordinate list is converted to their floating point + * values. It will then replace the current marker coordinates. + * + * Since different marker types require different number of coordinates + * this must be checked here. + * + * Results: + * The return value is a standard TCL result. + * + * Side effects: + * If the marker coordinates are reset, the graph is eventually redrawn + * with at the new marker coordinates. + * + *--------------------------------------------------------------------------- + */ +static int +ParseCoordinates( + Tcl_Interp *interp, + Marker *markerPtr, + int objc, + Tcl_Obj *const *objv) +{ + int nWorldPts; + int minArgs, maxArgs; + Point2d *worldPts; + int i; + + if (objc == 0) { + return TCL_OK; + } + if (objc & 1) { + Tcl_AppendResult(interp, "odd number of marker coordinates specified", + (char *)NULL); + return TCL_ERROR; + } + switch (markerPtr->obj.classId) { + case CID_MARKER_LINE: + minArgs = 4, maxArgs = 0; + break; + case CID_MARKER_POLYGON: + minArgs = 6, maxArgs = 0; + break; + case CID_MARKER_WINDOW: + case CID_MARKER_TEXT: + minArgs = 2, maxArgs = 2; + break; + case CID_MARKER_IMAGE: + case CID_MARKER_BITMAP: + minArgs = 2, maxArgs = 4; + break; + default: + Tcl_AppendResult(interp, "unknown marker type", (char *)NULL); + return TCL_ERROR; + } + + if (objc < minArgs) { + Tcl_AppendResult(interp, "too few marker coordinates specified", + (char *)NULL); + return TCL_ERROR; + } + if ((maxArgs > 0) && (objc > maxArgs)) { + Tcl_AppendResult(interp, "too many marker coordinates specified", + (char *)NULL); + return TCL_ERROR; + } + nWorldPts = objc / 2; + worldPts = Blt_Malloc(nWorldPts * sizeof(Point2d)); + if (worldPts == NULL) { + Tcl_AppendResult(interp, "can't allocate new coordinate array", + (char *)NULL); + return TCL_ERROR; + } + + { + Point2d *pp; + + pp = worldPts; + for (i = 0; i < objc; i += 2) { + double x, y; + + if ((GetCoordinate(interp, objv[i], &x) != TCL_OK) || + (GetCoordinate(interp, objv[i + 1], &y) != TCL_OK)) { + Blt_Free(worldPts); + return TCL_ERROR; + } + pp->x = x, pp->y = y, pp++; + } + } + /* Don't free the old coordinate array until we've parsed the new + * coordinates without errors. */ + if (markerPtr->worldPts != NULL) { + Blt_Free(markerPtr->worldPts); + } + markerPtr->worldPts = worldPts; + markerPtr->nWorldPts = nWorldPts; + markerPtr->flags |= MAP_ITEM; + return TCL_OK; +} + +/*ARGSUSED*/ +static void +FreeCoordsProc( + ClientData clientData, /* Not used. */ + Display *display, /* Not used. */ + char *widgRec, + int offset) +{ + Marker *markerPtr = (Marker *)widgRec; + Point2d **pointsPtr = (Point2d **)(widgRec + offset); + + if (*pointsPtr != NULL) { + Blt_Free(*pointsPtr); + *pointsPtr = NULL; + } + markerPtr->nWorldPts = 0; +} + +/* + *--------------------------------------------------------------------------- + * + * ObjToCoordsProc -- + * + * Given a TCL list of numeric expression representing the element + * values, convert into an array of floating point values. In addition, + * the minimum and maximum values are saved. Since elastic values are + * allow (values which translate to the min/max of the graph), we must + * try to get the non-elastic minimum and maximum. + * + * Results: + * The return value is a standard TCL result. The vector is + * passed back via the vecPtr. + * + * -------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +ObjToCoordsProc( + ClientData clientData, /* Not used. */ + Tcl_Interp *interp, /* Interpreter to return results */ + Tk_Window tkwin, /* Not used. */ + Tcl_Obj *objPtr, /* TCL list of numeric expressions */ + char *widgRec, /* Marker record */ + int offset, /* Not used. */ + int flags) /* Not used. */ +{ + Marker *markerPtr = (Marker *)widgRec; + int objc; + Tcl_Obj **objv; + + if (Tcl_ListObjGetElements(interp, objPtr, &objc, &objv) != TCL_OK) { + return TCL_ERROR; + } + if (objc == 0) { + return TCL_OK; + } + return ParseCoordinates(interp, markerPtr, objc, objv); +} + +/* + *--------------------------------------------------------------------------- + * + * CoordsToObjProc -- + * + * Convert the vector of floating point values into a TCL list. + * + * Results: + * The string representation of the vector is returned. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static Tcl_Obj * +CoordsToObjProc( + ClientData clientData, /* Not used. */ + Tcl_Interp *interp, + Tk_Window tkwin, /* Not used. */ + char *widgRec, /* Marker record */ + int offset, /* Not used. */ + int flags) /* Not used. */ +{ + Marker *markerPtr = (Marker *)widgRec; + Tcl_Obj *listObjPtr; + Point2d *pp, *pend; + + listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL); + for (pp = markerPtr->worldPts, pend = pp + markerPtr->nWorldPts; pp < pend; + pp++) { + Tcl_ListObjAppendElement(interp, listObjPtr, PrintCoordinate(pp->x)); + Tcl_ListObjAppendElement(interp, listObjPtr, PrintCoordinate(pp->y)); + } + return listObjPtr; +} + +/*LINTLIBRARY*/ +static int +GetColorPair( + Tcl_Interp *interp, + Tk_Window tkwin, + Tcl_Obj *fgObjPtr, Tcl_Obj *bgObjPtr, + ColorPair *pairPtr, + int allowDefault) +{ + XColor *fgColor, *bgColor; + const char *string; + + fgColor = bgColor = NULL; + if (fgObjPtr != NULL) { + int length; + + string = Tcl_GetStringFromObj(fgObjPtr, &length); + if (string[0] == '\0') { + fgColor = NULL; + } else if ((allowDefault) && (string[0] == 'd') && + (strncmp(string, "defcolor", length) == 0)) { + fgColor = COLOR_DEFAULT; + } else { + fgColor = Tk_AllocColorFromObj(interp, tkwin, fgObjPtr); + if (fgColor == NULL) { + return TCL_ERROR; + } + } + } + if (bgObjPtr != NULL) { + int length; + + string = Tcl_GetStringFromObj(bgObjPtr, &length); + if (string[0] == '\0') { + bgColor = NULL; + } else if ((allowDefault) && (string[0] == 'd') && + (strncmp(string, "defcolor", length) == 0)) { + bgColor = COLOR_DEFAULT; + } else { + bgColor = Tk_AllocColorFromObj(interp, tkwin, bgObjPtr); + if (bgColor == NULL) { + return TCL_ERROR; + } + } + } + if (pairPtr->fgColor != NULL) { + Tk_FreeColor(pairPtr->fgColor); + } + if (pairPtr->bgColor != NULL) { + Tk_FreeColor(pairPtr->bgColor); + } + pairPtr->fgColor = fgColor; + pairPtr->bgColor = bgColor; + return TCL_OK; +} + +void +Blt_FreeColorPair(ColorPair *pairPtr) +{ + if ((pairPtr->bgColor != NULL) && (pairPtr->bgColor != COLOR_DEFAULT)) { + Tk_FreeColor(pairPtr->bgColor); + } + if ((pairPtr->fgColor != NULL) && (pairPtr->fgColor != COLOR_DEFAULT)) { + Tk_FreeColor(pairPtr->fgColor); + } + pairPtr->bgColor = pairPtr->fgColor = NULL; +} + +static void +FreeColorPairProc( + ClientData clientData, /* Not used. */ + Display *display, /* Not used. */ + char *widgRec, + int offset) +{ + ColorPair *pairPtr = (ColorPair *)(widgRec + offset); + + Blt_FreeColorPair(pairPtr); +} + +/* + *--------------------------------------------------------------------------- + * + * ObjToColorPairProc -- + * + * Convert the color names into pair of XColor pointers. + * + * Results: + * A standard TCL result. The color pointer is written into the + * widget record. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +ObjToColorPairProc( + ClientData clientData, /* Not used. */ + Tcl_Interp *interp, /* Interpreter to return results */ + Tk_Window tkwin, /* Not used. */ + Tcl_Obj *objPtr, /* String representing color */ + char *widgRec, /* Widget record */ + int offset, /* Offset to field in structure */ + int flags) /* Not used. */ +{ + ColorPair *pairPtr = (ColorPair *)(widgRec + offset); + long longValue = (long)clientData; + int bool; + int objc; + Tcl_Obj **objv; + + if (Tcl_ListObjGetElements(interp, objPtr, &objc, &objv) != TCL_OK) { + return TCL_ERROR; + } + if (objc > 2) { + Tcl_AppendResult(interp, "too many names in colors list", + (char *)NULL); + return TCL_ERROR; + } + if (objc == 0) { + Blt_FreeColorPair(pairPtr); + return TCL_OK; + } + bool = (int)longValue; + if (objc == 1) { + if (GetColorPair(interp, tkwin, objv[0], NULL, pairPtr, bool) + != TCL_OK) { + return TCL_ERROR; + } + } else { + if (GetColorPair(interp, tkwin, objv[0], objv[1], pairPtr, bool) + != TCL_OK) { + return TCL_ERROR; + } + } + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * NameOfColor -- + * + * Convert the color option value into a string. + * + * Results: + * The static string representing the color option is returned. + * + *--------------------------------------------------------------------------- + */ +static const char * +NameOfColor(XColor *colorPtr) +{ + if (colorPtr == NULL) { + return ""; + } else if (colorPtr == COLOR_DEFAULT) { + return "defcolor"; + } else { + return Tk_NameOfColor(colorPtr); + } +} + +/* + *--------------------------------------------------------------------------- + * + * ColorPairToObjProc -- + * + * Convert the color pairs into color names. + * + * Results: + * The string representing the symbol color is returned. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static Tcl_Obj * +ColorPairToObjProc( + ClientData clientData, /* Not used. */ + Tcl_Interp *interp, + Tk_Window tkwin, /* Not used. */ + char *widgRec, /* Element information record */ + int offset, /* Offset to field in structure */ + int flags) /* Not used. */ +{ + ColorPair *pairPtr = (ColorPair *)(widgRec + offset); + Tcl_Obj *listObjPtr; + + listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL); + Tcl_ListObjAppendElement(interp, listObjPtr, + Tcl_NewStringObj(NameOfColor(pairPtr->fgColor), -1)); + Tcl_ListObjAppendElement(interp, listObjPtr, + Tcl_NewStringObj(NameOfColor(pairPtr->bgColor), -1)); + return listObjPtr; +} + +/* + *--------------------------------------------------------------------------- + * + * ImageChangedProc + * + * + * Results: + * None. + * + *--------------------------------------------------------------------------- + */ +/* ARGSUSED */ +static void +ImageChangedProc( + ClientData clientData, + int x, int y, int w, int h, /* Not used. */ + int imageWidth, int imageHeight) /* Not used. */ +{ + Graph *graphPtr; + ImageMarker *imPtr = clientData; + int isPhoto; + + graphPtr = imPtr->obj.graphPtr; + if ((imPtr->picture != NULL) && (imPtr->flags & IMAGE_PHOTO)) { + Blt_FreePicture(imPtr->picture); + } + imPtr->picture = NULL; + imPtr->flags &= ~IMAGE_PHOTO; + if (Blt_Image_IsDeleted(imPtr->tkImage)) { + Tk_FreeImage(imPtr->tkImage); + imPtr->tkImage = NULL; + return; + } + imPtr->picture = Blt_GetPictureFromImage(graphPtr->interp, imPtr->tkImage, + &isPhoto); + if (isPhoto) { + imPtr->flags |= IMAGE_PHOTO; + } + graphPtr->flags |= CACHE_DIRTY; + imPtr->flags |= MAP_ITEM; + Blt_EventuallyRedrawGraph(graphPtr); +} + +/*ARGSUSED*/ +static void +FreePictImageProc( + ClientData clientData, + Display *display, /* Not used. */ + char *widgRec, + int offset) +{ + ImageMarker *imPtr = (ImageMarker *)widgRec; + + if ((imPtr->picture != NULL) && (imPtr->flags & IMAGE_PHOTO)) { + Blt_FreePicture(imPtr->picture); + } + imPtr->picture = NULL; + if (imPtr->tkImage != NULL) { + Tk_FreeImage(imPtr->tkImage); + } + imPtr->tkImage = NULL; + imPtr->flags &= ~IMAGE_PHOTO; +} + +/* + *--------------------------------------------------------------------------- + * + * ObjToPictImageProc -- + * + * Given an image name, get the Tk image associated with it. + * + * Results: + * The return value is a standard TCL result. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +ObjToPictImageProc( + ClientData clientData, /* Not used. */ + Tcl_Interp *interp, /* Interpreter to return results */ + Tk_Window tkwin, /* Not used. */ + Tcl_Obj *objPtr, /* String representation of value. */ + char *widgRec, /* Widget record. */ + int offset, /* Offset to field in structure */ + int flags) +{ + Blt_Picture *picturePtr = (Blt_Picture *)(widgRec + offset); + Graph *graphPtr; + ImageMarker *imPtr = (ImageMarker *)widgRec; + Tk_Image tkImage; + const char *name; + int isPhoto; + + name = Tcl_GetString(objPtr); + tkImage = Tk_GetImage(interp, tkwin, name, ImageChangedProc, imPtr); + if (tkImage == NULL) { + return TCL_ERROR; + } + if (imPtr->tkImage != NULL) { + Tk_FreeImage(imPtr->tkImage); + } + imPtr->flags &= ~IMAGE_PHOTO; + if (*picturePtr != NULL) { + Blt_FreePicture(*picturePtr); + } + *picturePtr = NULL; + imPtr->tkImage = tkImage; + graphPtr = imPtr->obj.graphPtr; + *picturePtr = Blt_GetPictureFromImage(graphPtr->interp, tkImage, &isPhoto); + if (isPhoto) { + imPtr->flags |= IMAGE_PHOTO; + } + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * PictImageToObjProc -- + * + * Convert the image name into a string Tcl_Obj. + * + * Results: + * The string representation of the image is returned. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static Tcl_Obj * +PictImageToObjProc( + ClientData clientData, /* Not used. */ + Tcl_Interp *interp, + Tk_Window tkwin, /* Not used. */ + char *widgRec, /* Widget record */ + int offset, /* Offset to field in structure */ + int flags) +{ + ImageMarker *imPtr = (ImageMarker *)(widgRec); + + if (imPtr->tkImage == NULL) { + return Tcl_NewStringObj("", -1); + } + return Tcl_NewStringObj(Blt_Image_Name(imPtr->tkImage), -1); +} + +static INLINE int +IsElementHidden(Marker *markerPtr) +{ + Blt_HashEntry *hPtr; + Graph *graphPtr = markerPtr->obj.graphPtr; + + /* Look up the named element and see if it's hidden */ + hPtr = Blt_FindHashEntry(&graphPtr->elements.table, markerPtr->elemName); + if (hPtr != NULL) { + Element *elemPtr; + + elemPtr = Blt_GetHashValue(hPtr); + if ((elemPtr->link == NULL) || (elemPtr->flags & HIDE)) { + return TRUE; + } + } + return FALSE; +} + +/* + *--------------------------------------------------------------------------- + * + * HMap -- + * + * Maps the given graph coordinate value to its axis, returning a window + * position. This is a slight variation on the normal Blt_HMap routine. + * It treats -Inf as the minimum axis value and Inf as the maximum. + * + * Results: + * Returns a floating point number representing the window coordinate + * position on the given axis. + * + * -------------------------------------------------------------------------- + */ +static double +HMap(Axis *axisPtr, double x) +{ + if (x == DBL_MAX) { + x = 1.0; + } else if (x == -DBL_MAX) { + x = 0.0; + } else { + if (axisPtr->logScale) { + if (x > 0.0) { + x = log10(x); + } else if (x < 0.0) { + x = 0.0; + } + } + x = NORMALIZE(axisPtr, x); + } + if (axisPtr->descending) { + x = 1.0 - x; + } + /* Horizontal transformation */ + return (x * axisPtr->screenRange + axisPtr->screenMin); +} + +/* + *--------------------------------------------------------------------------- + * + * VMap -- + * + * Map the given graph coordinate value to its axis, returning a window + * position. This is a slight variation on the normal Blt_VMap routine. + * It treats -Inf as the minimum axis value and Inf as the maximum. + * + * Results: + * Returns a double precision number representing the window coordinate + * position on the given axis. + * + *--------------------------------------------------------------------------- + */ +static double +VMap(Axis *axisPtr, double y) +{ + if (y == DBL_MAX) { + y = 1.0; + } else if (y == -DBL_MAX) { + y = 0.0; + } else { + if (axisPtr->logScale) { + if (y > 0.0) { + y = log10(y); + } else if (y < 0.0) { + y = 0.0; + } + } + y = NORMALIZE(axisPtr, y); + } + if (axisPtr->descending) { + y = 1.0 - y; + } + /* Vertical transformation. */ + return (((1.0 - y) * axisPtr->screenRange) + axisPtr->screenMin); +} + +/* + *--------------------------------------------------------------------------- + * + * MapPoint -- + * + * Maps the given graph x,y coordinate values to a window position. + * + * Results: + * Returns a XPoint structure containing the window coordinates of the + * given graph x,y coordinate. + * + *--------------------------------------------------------------------------- + */ +static Point2d +MapPoint( + Point2d *pointPtr, /* Graph X-Y coordinate. */ + Axis2d *axesPtr) /* Specifies which axes to use */ +{ + Point2d result; + Graph *graphPtr = axesPtr->y->obj.graphPtr; + + if (graphPtr->inverted) { + result.x = HMap(axesPtr->y, pointPtr->y); + result.y = VMap(axesPtr->x, pointPtr->x); + } else { + result.x = HMap(axesPtr->x, pointPtr->x); + result.y = VMap(axesPtr->y, pointPtr->y); + } + return result; /* Result is screen coordinate. */ +} + +static Marker * +CreateMarker( + Graph *graphPtr, + const char *name, + ClassId classId) +{ + Marker *markerPtr; + + /* Create the new marker based upon the given type */ + switch (classId) { + case CID_MARKER_BITMAP: + markerPtr = CreateBitmapProc(); /* bitmap */ + break; + case CID_MARKER_LINE: + markerPtr = CreateLineProc(); /* line */ + break; + case CID_MARKER_IMAGE: + markerPtr = CreateImageProc(); /* image */ + break; + case CID_MARKER_TEXT: + markerPtr = CreateTextProc(); /* text */ + break; + case CID_MARKER_POLYGON: + markerPtr = CreatePolygonProc(); /* polygon */ + break; + case CID_MARKER_WINDOW: + markerPtr = CreateWindowProc(); /* window */ + break; + default: + return NULL; + } + markerPtr->obj.graphPtr = graphPtr; + markerPtr->drawUnder = FALSE; + markerPtr->flags |= MAP_ITEM; + markerPtr->obj.name = Blt_AssertStrdup(name); + Blt_GraphSetObjectClass(&markerPtr->obj, classId); + return markerPtr; +} + + +static void +DestroyMarker(Marker *markerPtr) +{ + Graph *graphPtr = markerPtr->obj.graphPtr; + + if (markerPtr->drawUnder) { + /* If the marker to be deleted is currently displayed below the + * elements, then backing store needs to be repaired. */ + graphPtr->flags |= CACHE_DIRTY; + } + /* + * Call the marker's type-specific deallocation routine. We do it first + * while all the marker fields are still valid. + */ + (*markerPtr->classPtr->freeProc)(markerPtr); + + /* Dump any bindings that might be registered for the marker. */ + Blt_DeleteBindings(graphPtr->bindTable, markerPtr); + + /* Release all the X resources associated with the marker. */ + Blt_FreeOptions(markerPtr->classPtr->configSpecs, (char *)markerPtr, + graphPtr->display, 0); + + if (markerPtr->hashPtr != NULL) { + Blt_DeleteHashEntry(&graphPtr->markers.table, + markerPtr->hashPtr); + } + if (markerPtr->link != NULL) { + Blt_Chain_DeleteLink(graphPtr->markers.displayList, markerPtr->link); + } + if (markerPtr->obj.name != NULL) { + Blt_Free(markerPtr->obj.name); + } + Blt_Free(markerPtr); +} + +static void +FreeMarker(DestroyData dataPtr) +{ + Marker *markerPtr = (Marker *)dataPtr; + DestroyMarker(markerPtr); +} + +/* + *--------------------------------------------------------------------------- + * + * ConfigureBitmapProc -- + * + * This procedure is called to process an objv/objc list, plus the Tk + * option database, in order to configure (or reconfigure) a bitmap + * marker. + * + * Results: + * A standard TCL result. If TCL_ERROR is returned, then interp->result + * contains an error message. + * + * Side effects: + * Configuration information, such as bitmap pixmap, colors, rotation, + * etc. get set for markerPtr; old resources get freed, if there were + * any. The marker is eventually redisplayed. + * + *--------------------------------------------------------------------------- + */ +/* ARGSUSED */ +static int +ConfigureBitmapProc(Marker *markerPtr) +{ + Graph *graphPtr = markerPtr->obj.graphPtr; + BitmapMarker *bmPtr = (BitmapMarker *)markerPtr; + GC newGC; + XGCValues gcValues; + unsigned long gcMask; + + if (bmPtr->srcBitmap == None) { + return TCL_OK; + } + bmPtr->angle = FMOD(bmPtr->reqAngle, 360.0); + if (bmPtr->angle < 0.0) { + bmPtr->angle += 360.0; + } + gcMask = 0; + + if (bmPtr->outlineColor != NULL) { + gcMask |= GCForeground; + gcValues.foreground = bmPtr->outlineColor->pixel; + } + + if (bmPtr->fillColor != NULL) { + /* Opaque bitmap: both foreground and background (fill) colors + * are used. */ + gcValues.background = bmPtr->fillColor->pixel; + gcMask |= GCBackground; + } else { + /* Transparent bitmap: set the clip mask to the current bitmap. */ + gcValues.clip_mask = bmPtr->srcBitmap; + gcMask |= GCClipMask; + } + + /* + * This is technically a shared GC, but we're going to set/change the clip + * origin anyways before we draw the bitmap. This relies on the fact that + * no other client will be allocated this GC with the GCClipMask set to + * this particular bitmap. + */ + newGC = Tk_GetGC(graphPtr->tkwin, gcMask, &gcValues); + if (bmPtr->gc != NULL) { + Tk_FreeGC(graphPtr->display, bmPtr->gc); + } + bmPtr->gc = newGC; + + /* Create the background GC containing the fill color. */ + + if (bmPtr->fillColor != NULL) { + gcValues.foreground = bmPtr->fillColor->pixel; + newGC = Tk_GetGC(graphPtr->tkwin, gcMask, &gcValues); + if (bmPtr->fillGC != NULL) { + Tk_FreeGC(graphPtr->display, bmPtr->fillGC); + } + bmPtr->fillGC = newGC; + } + + markerPtr->flags |= MAP_ITEM; + if (markerPtr->drawUnder) { + graphPtr->flags |= CACHE_DIRTY; + } + + Blt_EventuallyRedrawGraph(graphPtr); + return TCL_OK; +} + +#ifdef notdef +static void +PrintPolyPoint(char *mesg, Point2d *points, int nPoints) +{ + int i; + + fprintf(stderr, "%s:\t\tpoint[0]=%g,%g\n", mesg, points[0].x, points[0].y); + for (i = 1; i < nPoints; i++) { + fprintf(stderr, "\t\tpoint[%d]=%g,%g\n", i, points[i].x, points[i].y); + } +} +#endif + +/* + *--------------------------------------------------------------------------- + * + * MapBitmapProc -- + * + * This procedure gets called each time the layout of the graph changes. + * The x, y window coordinates of the bitmap marker are saved in the + * marker structure. + * + * Additionly, if no background color was specified, the + * GCTileStipXOrigin and GCTileStipYOrigin attributes are set in the + * private GC. + * + * Results: + * None. + * + * Side effects: + * Window coordinates are saved and if no background color was set, the + * GC stipple origins are changed to calculated window coordinates. + * + *--------------------------------------------------------------------------- + */ +static void +MapBitmapProc(Marker *markerPtr) +{ + BitmapMarker *bmPtr = (BitmapMarker *)markerPtr; + Region2d extents; + Graph *graphPtr = markerPtr->obj.graphPtr; + Point2d anchorPt; + Point2d corner1, corner2; + int destWidth, destHeight; + int srcWidth, srcHeight; + int i; + + if (bmPtr->srcBitmap == None) { + return; + } + if (bmPtr->destBitmap != None) { + Tk_FreePixmap(graphPtr->display, bmPtr->destBitmap); + bmPtr->destBitmap = None; + } + /* + * Collect the coordinates. The number of coordinates will determine the + * calculations to be made. + * + * x1 y1 A single pair of X-Y coordinates. They represent + * the anchor position of the bitmap. + * + * x1 y1 x2 y2 Two pairs of X-Y coordinates. They represent + * two opposite corners of a bounding rectangle. The + * bitmap is possibly rotated and scaled to fit into + * this box. + * + */ + Tk_SizeOfBitmap(graphPtr->display, bmPtr->srcBitmap, &srcWidth, + &srcHeight); + corner1 = MapPoint(markerPtr->worldPts, &markerPtr->axes); + if (markerPtr->nWorldPts > 1) { + double hold; + + corner2 = MapPoint(markerPtr->worldPts + 1, &markerPtr->axes); + /* Flip the corners if necessary */ + if (corner1.x > corner2.x) { + hold = corner1.x, corner1.x = corner2.x, corner2.x = hold; + } + if (corner1.y > corner2.y) { + hold = corner1.y, corner1.y = corner2.y, corner2.y = hold; + } + } else { + corner2.x = corner1.x + srcWidth - 1; + corner2.y = corner1.y + srcHeight - 1; + } + destWidth = (int)(corner2.x - corner1.x) + 1; + destHeight = (int)(corner2.y - corner1.y) + 1; + + if (markerPtr->nWorldPts == 1) { + anchorPt = Blt_AnchorPoint(corner1.x, corner1.y, (double)destWidth, + (double)destHeight, bmPtr->anchor); + } else { + anchorPt = corner1; + } + anchorPt.x += markerPtr->xOffset; + anchorPt.y += markerPtr->yOffset; + + /* Check if the bitmap sits at least partially in the plot area. */ + extents.left = anchorPt.x; + extents.top = anchorPt.y; + extents.right = anchorPt.x + destWidth - 1; + extents.bottom = anchorPt.y + destHeight - 1; + markerPtr->clipped = BoxesDontOverlap(graphPtr, &extents); + if (markerPtr->clipped) { + return; /* Bitmap is offscreen. Don't generate + * rotated or scaled bitmaps. */ + } + + /* + * Scale the bitmap if necessary. It's a little tricky because we only + * want to scale what's visible on the screen, not the entire bitmap. + */ + if ((bmPtr->angle != 0.0f) || (destWidth != srcWidth) || + (destHeight != srcHeight)) { + int regionX, regionY, regionWidth, regionHeight; + double left, right, top, bottom; + + /* Ignore parts of the bitmap outside of the plot area. */ + left = MAX(graphPtr->left, extents.left); + right = MIN(graphPtr->right, extents.right); + top = MAX(graphPtr->top, extents.top); + bottom = MIN(graphPtr->bottom, extents.bottom); + + /* Determine the portion of the scaled bitmap to display. */ + regionX = regionY = 0; + if (graphPtr->left > extents.left) { + regionX = (int)(graphPtr->left - extents.left); + } + if (graphPtr->top > extents.top) { + regionY = (int)(graphPtr->top - extents.top); + } + regionWidth = (int)(right - left) + 1; + regionHeight = (int)(bottom - top) + 1; + + anchorPt.x = left; + anchorPt.y = top; + bmPtr->destBitmap = Blt_ScaleRotateBitmapArea(graphPtr->tkwin, + bmPtr->srcBitmap, srcWidth, srcHeight, regionX, regionY, + regionWidth, regionHeight, destWidth, destHeight, bmPtr->angle); + bmPtr->destWidth = regionWidth; + bmPtr->destHeight = regionHeight; + } else { + bmPtr->destWidth = srcWidth; + bmPtr->destHeight = srcHeight; + bmPtr->destBitmap = None; + } + bmPtr->anchorPt = anchorPt; + { + double xScale, yScale; + double tx, ty; + double rotWidth, rotHeight; + Point2d polygon[5]; + int n; + + /* + * Compute a polygon to represent the background area of the bitmap. + * This is needed for backgrounds of arbitrarily rotated bitmaps. We + * also use it to print a background in PostScript. + */ + Blt_GetBoundingBox(srcWidth, srcHeight, bmPtr->angle, &rotWidth, + &rotHeight, polygon); + xScale = (double)destWidth / rotWidth; + yScale = (double)destHeight / rotHeight; + + /* + * Adjust each point of the polygon. Both scale it to the new size and + * translate it to the actual screen position of the bitmap. + */ + tx = extents.left + destWidth * 0.5; + ty = extents.top + destHeight * 0.5; + for (i = 0; i < 4; i++) { + polygon[i].x = (polygon[i].x * xScale) + tx; + polygon[i].y = (polygon[i].y * yScale) + ty; + } + Blt_GraphExtents(graphPtr, &extents); + n = Blt_PolyRectClip(&extents, polygon, 4, bmPtr->outline); + assert(n <= MAX_OUTLINE_POINTS); + if (n < 3) { + memcpy(&bmPtr->outline, polygon, sizeof(Point2d) * 4); + bmPtr->nOutlinePts = 4; + } else { + bmPtr->nOutlinePts = n; + } + } +} + +/* + *--------------------------------------------------------------------------- + * + * PointInBitmapProc -- + * + * Indicates if the given point is over the bitmap marker. The area of + * the bitmap is the rectangle. + * + * Results: + * Returns 1 is the point is over the bitmap marker, 0 otherwise. + * + *--------------------------------------------------------------------------- + */ +static int +PointInBitmapProc(Marker *markerPtr, Point2d *samplePtr) +{ + BitmapMarker *bmPtr = (BitmapMarker *)markerPtr; + + if (bmPtr->srcBitmap == None) { + return 0; + } + if (bmPtr->angle != 0.0f) { + Point2d points[MAX_OUTLINE_POINTS]; + int i; + + /* + * Generate the bounding polygon (isolateral) for the bitmap and see + * if the point is inside of it. + */ + for (i = 0; i < bmPtr->nOutlinePts; i++) { + points[i].x = bmPtr->outline[i].x + bmPtr->anchorPt.x; + points[i].y = bmPtr->outline[i].y + bmPtr->anchorPt.y; + } + return Blt_PointInPolygon(samplePtr, points, bmPtr->nOutlinePts); + } + return ((samplePtr->x >= bmPtr->anchorPt.x) && + (samplePtr->x < (bmPtr->anchorPt.x + bmPtr->destWidth)) && + (samplePtr->y >= bmPtr->anchorPt.y) && + (samplePtr->y < (bmPtr->anchorPt.y + bmPtr->destHeight))); +} + + +/* + *--------------------------------------------------------------------------- + * + * RegionInBitmapProc -- + * + *--------------------------------------------------------------------------- + */ +static int +RegionInBitmapProc(Marker *markerPtr, Region2d *extsPtr, int enclosed) +{ + BitmapMarker *bmPtr = (BitmapMarker *)markerPtr; + + if (markerPtr->nWorldPts < 1) { + return FALSE; + } + if (bmPtr->angle != 0.0f) { + Point2d points[MAX_OUTLINE_POINTS]; + int i; + + /* + * Generate the bounding polygon (isolateral) for the bitmap and see + * if the point is inside of it. + */ + for (i = 0; i < bmPtr->nOutlinePts; i++) { + points[i].x = bmPtr->outline[i].x + bmPtr->anchorPt.x; + points[i].y = bmPtr->outline[i].y + bmPtr->anchorPt.y; + } + return Blt_RegionInPolygon(extsPtr, points, bmPtr->nOutlinePts, + enclosed); + } + if (enclosed) { + return ((bmPtr->anchorPt.x >= extsPtr->left) && + (bmPtr->anchorPt.y >= extsPtr->top) && + ((bmPtr->anchorPt.x + bmPtr->destWidth) <= extsPtr->right) && + ((bmPtr->anchorPt.y + bmPtr->destHeight) <= extsPtr->bottom)); + } + return !((bmPtr->anchorPt.x >= extsPtr->right) || + (bmPtr->anchorPt.y >= extsPtr->bottom) || + ((bmPtr->anchorPt.x + bmPtr->destWidth) <= extsPtr->left) || + ((bmPtr->anchorPt.y + bmPtr->destHeight) <= extsPtr->top)); +} + +/* + *--------------------------------------------------------------------------- + * + * DrawBitmapProc -- + * + * Draws the bitmap marker that have a transparent of filled background. + * + * Results: + * None. + * + * Side effects: + * GC stipple origins are changed to current window coordinates. + * Commands are output to X to draw the marker in its current mode. + * + *--------------------------------------------------------------------------- + */ +static void +DrawBitmapProc(Marker *markerPtr, Drawable drawable) +{ + Graph *graphPtr = markerPtr->obj.graphPtr; + BitmapMarker *bmPtr = (BitmapMarker *)markerPtr; + double rangle; + Pixmap bitmap; + + bitmap = GETBITMAP(bmPtr); + if ((bitmap == None) || (bmPtr->destWidth < 1) || (bmPtr->destHeight < 1)) { + return; + } + rangle = FMOD(bmPtr->angle, 90.0); + if ((bmPtr->fillColor == NULL) || (rangle != 0.0)) { + + /* + * If the bitmap is rotated and a filled background is required, then + * a filled polygon is drawn before the bitmap. + */ + if (bmPtr->fillColor != NULL) { + int i; + XPoint polygon[MAX_OUTLINE_POINTS]; + + for (i = 0; i < bmPtr->nOutlinePts; i++) { + polygon[i].x = (short int)bmPtr->outline[i].x; + polygon[i].y = (short int)bmPtr->outline[i].y; + } + XFillPolygon(graphPtr->display, drawable, bmPtr->fillGC, + polygon, bmPtr->nOutlinePts, Convex, CoordModeOrigin); + } + XSetClipMask(graphPtr->display, bmPtr->gc, bitmap); + XSetClipOrigin(graphPtr->display, bmPtr->gc, (int)bmPtr->anchorPt.x, + (int)bmPtr->anchorPt.y); + } else { + XSetClipMask(graphPtr->display, bmPtr->gc, None); + XSetClipOrigin(graphPtr->display, bmPtr->gc, 0, 0); + } + XCopyPlane(graphPtr->display, bitmap, drawable, bmPtr->gc, 0, 0, + bmPtr->destWidth, bmPtr->destHeight, (int)bmPtr->anchorPt.x, + (int)bmPtr->anchorPt.y, 1); +} + +/* + *--------------------------------------------------------------------------- + * + * BitmapToPostscriptProc -- + * + * Generates PostScript to print a bitmap marker. + * + * Results: + * None. + * + *--------------------------------------------------------------------------- + */ +static void +BitmapToPostscriptProc(Marker *markerPtr, Blt_Ps ps) +{ + Graph *graphPtr = markerPtr->obj.graphPtr; + BitmapMarker *bmPtr = (BitmapMarker *)markerPtr; + Pixmap bitmap; + + bitmap = GETBITMAP(bmPtr); + if ((bitmap == None) || (bmPtr->destWidth < 1) || (bmPtr->destHeight < 1)) { + return; /* No bitmap to display. */ + } + if (bmPtr->fillColor != NULL) { + Blt_Ps_XSetBackground(ps, bmPtr->fillColor); + Blt_Ps_XFillPolygon(ps, bmPtr->outline, 4); + } + Blt_Ps_XSetForeground(ps, bmPtr->outlineColor); + + Blt_Ps_Format(ps, + " gsave\n %g %g translate\n %d %d scale\n", + bmPtr->anchorPt.x, bmPtr->anchorPt.y + bmPtr->destHeight, + bmPtr->destWidth, -bmPtr->destHeight); + Blt_Ps_Format(ps, " %d %d true [%d 0 0 %d 0 %d] {", + bmPtr->destWidth, bmPtr->destHeight, bmPtr->destWidth, + -bmPtr->destHeight, bmPtr->destHeight); + Blt_Ps_XSetBitmapData(ps, graphPtr->display, bitmap, + bmPtr->destWidth, bmPtr->destHeight); + Blt_Ps_VarAppend(ps, + " } imagemask\n", + "grestore\n", (char *)NULL); +} + +/* + *--------------------------------------------------------------------------- + * + * FreeBitmapProc -- + * + * Releases the memory and attributes of the bitmap marker. + * + * Results: + * None. + * + * Side effects: + * Bitmap attributes (GCs, colors, bitmap, etc) get destroyed. Memory is + * released, X resources are freed, and the graph is redrawn. + * + *--------------------------------------------------------------------------- + */ +static void +FreeBitmapProc(Marker *markerPtr) +{ + BitmapMarker *bmPtr = (BitmapMarker *)markerPtr; + Graph *graphPtr = markerPtr->obj.graphPtr; + + if (bmPtr->gc != NULL) { + Tk_FreeGC(graphPtr->display, bmPtr->gc); + } + if (bmPtr->fillGC != NULL) { + Tk_FreeGC(graphPtr->display, bmPtr->fillGC); + } + if (bmPtr->destBitmap != None) { + Tk_FreePixmap(graphPtr->display, bmPtr->destBitmap); + } +} + +/* + *--------------------------------------------------------------------------- + * + * CreateBitmapProc -- + * + * Allocate memory and initialize methods for the new bitmap marker. + * + * Results: + * The pointer to the newly allocated marker structure is returned. + * + * Side effects: + * Memory is allocated for the bitmap marker structure. + * + *--------------------------------------------------------------------------- + */ +static Marker * +CreateBitmapProc(void) +{ + BitmapMarker *bmPtr; + + bmPtr = Blt_AssertCalloc(1, sizeof(BitmapMarker)); + bmPtr->classPtr = &bitmapMarkerClass; + return (Marker *)bmPtr; +} + + +/* + *--------------------------------------------------------------------------- + * + * ConfigureImageProc -- + * + * This procedure is called to process an objv/objc list, plus the Tk + * option database, in order to configure (or reconfigure) a image + * marker. + * + * Results: + * A standard TCL result. If TCL_ERROR is returned, then interp->result + * contains an error message. + * + * Side effects: + * Configuration information, such as image pixmap, colors, rotation, + * etc. get set for markerPtr; old resources get freed, if there were + * any. The marker is eventually redisplayed. + * + *--------------------------------------------------------------------------- + */ +static int +ConfigureImageProc(Marker *markerPtr) +{ + ImageMarker *imPtr = (ImageMarker *)markerPtr; + Graph *graphPtr = markerPtr->obj.graphPtr; + Blt_Painter painter; + GC newGC; + + newGC = Tk_GetGC(graphPtr->tkwin, 0L, (XGCValues *)NULL); + if (imPtr->gc != NULL) { + Tk_FreeGC(graphPtr->display, imPtr->gc); + } + imPtr->gc = newGC; + + painter = Blt_GetPainter(graphPtr->tkwin, 1.0); + if (imPtr->painter != NULL) { + Blt_FreePainter(painter); + } + imPtr->painter = painter; + markerPtr->flags |= MAP_ITEM; + if (markerPtr->drawUnder) { + graphPtr->flags |= CACHE_DIRTY; + } + Blt_EventuallyRedrawGraph(graphPtr); + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * MapImageProc -- + * + * This procedure gets called each time the layout of the graph changes. + * The x, y window coordinates of the image marker are saved in the + * marker structure. + * + * In addition, if no background color was specified, the + * GCTileStipXOrigin and GCTileStipYOrigin attributes will not set in the + * private GC. + * + * Results: + * None. + * + * Side effects: + * Window coordinates are saved and if no background color was set, the + * GC stipple origins are changed to calculated window coordinates. + * + *--------------------------------------------------------------------------- + */ +static void +MapImageProc(Marker *markerPtr) +{ + Region2d extents; + Graph *graphPtr; + ImageMarker *imPtr; + Point2d anchorPt; + Point2d c1, c2; + int newWidth, newHeight; + int srcWidth, srcHeight; + int x, y, w, h; + int left, right, top, bottom; + + imPtr = (ImageMarker *)markerPtr; + if (imPtr->picture == NULL) { + return; + } + if (imPtr->scaled != NULL) { + Blt_FreePicture(imPtr->scaled); + imPtr->scaled = NULL; + } + graphPtr = markerPtr->obj.graphPtr; + c1 = MapPoint(markerPtr->worldPts, &markerPtr->axes); + + imPtr->width = srcWidth = Blt_PictureWidth(imPtr->picture); + imPtr->height = srcHeight = Blt_PictureHeight(imPtr->picture); + + if ((srcWidth == 0) || (srcHeight == 0)) { + markerPtr->clipped = TRUE; + return; /* Empty image. */ + } + if (markerPtr->nWorldPts > 1) { + double hold; + + c2 = MapPoint(markerPtr->worldPts + 1, &markerPtr->axes); + /* Flip the corners if necessary */ + if (c1.x > c2.x) { + hold = c1.x, c1.x = c2.x, c2.x = hold; + } + if (c1.y > c2.y) { + hold = c1.y, c1.y = c2.y, c2.y = hold; + } + } else { + c2.x = c1.x + srcWidth - 1; + c2.y = c1.y + srcHeight - 1; + } + newWidth = (int)(c2.x - c1.x) + 1; + newHeight = (int)(c2.y - c1.y) + 1; + + if (markerPtr->nWorldPts == 1) { + anchorPt = Blt_AnchorPoint(c1.x, c1.y, (double)newWidth, + (double)newHeight, imPtr->anchor); + } else { + anchorPt = c1; + } + anchorPt.x += markerPtr->xOffset; + anchorPt.y += markerPtr->yOffset; + + /* Check if the image sits at least partially in the plot area. */ + extents.left = anchorPt.x; + extents.top = anchorPt.y; + extents.right = anchorPt.x + newWidth - 1; + extents.bottom = anchorPt.y + newHeight - 1; + + markerPtr->clipped = BoxesDontOverlap(graphPtr, &extents); + if (markerPtr->clipped) { + return; /* Image is offscreen. Don't generate + * rotated or scaled images. */ + } + + /* Determine the extents of the subimage inside of the destination + * image. */ + left = MAX((int)extents.left, graphPtr->left); + top = MAX((int)extents.top, graphPtr->top); + right = MIN((int)extents.right, graphPtr->right); + bottom = MIN((int)extents.bottom, graphPtr->bottom); + + /* Reset image location and coordinates to that of the region */ + anchorPt.x = left; + anchorPt.y = top; + + x = y = 0; + if (graphPtr->left > (int)extents.left) { + x = graphPtr->left - (int)extents.left; + } + if (graphPtr->top > (int)extents.top) { + y = graphPtr->top - (int)extents.top; + } + w = (int)(right - left + 1); + h = (int)(bottom - top + 1); + + if (markerPtr->nWorldPts > 1) { + Blt_Picture scaled; + + scaled = Blt_ScalePictureArea(imPtr->picture, x, y, w, h, + newWidth, newHeight); + imPtr->scaled = scaled; + imPtr->pictX = 0; + imPtr->pictY = 0; + } else { + imPtr->pictX = x; + imPtr->pictY = y; + } + imPtr->width = newWidth; + imPtr->height = newHeight; + imPtr->anchorPt = anchorPt; +} + +/* + *--------------------------------------------------------------------------- + * + * PointInWindowProc -- + * + * Indicates if the given point is over the window marker. The area of + * the window is the rectangle. + * + * Results: + * Returns 1 is the point is over the window marker, 0 otherwise. + * + *--------------------------------------------------------------------------- + */ +static int +PointInImageProc(Marker *markerPtr, Point2d *samplePtr) +{ + ImageMarker *imPtr = (ImageMarker *)markerPtr; + double left, right, top, bottom; + + left = imPtr->anchorPt.x; + right = imPtr->anchorPt.x + imPtr->width; + top = imPtr->anchorPt.y; + bottom = imPtr->anchorPt.y + imPtr->height; + + return ((samplePtr->x >= left) && (samplePtr->x < right) && + (samplePtr->y >= top) && (samplePtr->y < bottom)); +} + +/* + *--------------------------------------------------------------------------- + * + * RegionInImageProc -- + * + *--------------------------------------------------------------------------- + */ +static int +RegionInImageProc(Marker *markerPtr, Region2d *regPtr, int enclosed) +{ + ImageMarker *imPtr = (ImageMarker *)markerPtr; + + if (markerPtr->nWorldPts > 0) { + double left, right, top, bottom; + + left = imPtr->anchorPt.x; + right = imPtr->anchorPt.x + imPtr->width; + top = imPtr->anchorPt.y; + bottom = imPtr->anchorPt.y + imPtr->height; + if (enclosed) { + return ((left >= regPtr->left) && (top >= regPtr->top) && + (right <= regPtr->right) && (bottom <= regPtr->bottom)); + } + return !((left >= regPtr->right) || (top >= regPtr->bottom) || + (right <= regPtr->left) || (bottom <= regPtr->top)); + } + return FALSE; +} + +/* + *--------------------------------------------------------------------------- + * + * DrawImageProc -- + * + * This procedure is invoked to draw a image marker. + * + * Results: + * None. + * + * Side effects: + * GC stipple origins are changed to current window coordinates. + * Commands are output to X to draw the marker in its current mode. + * + *--------------------------------------------------------------------------- + */ +static void +DrawImageProc(Marker *markerPtr, Drawable drawable) +{ + ImageMarker *imPtr = (ImageMarker *)markerPtr; + Blt_Picture picture; + + picture = (imPtr->scaled != NULL) ? imPtr->scaled : imPtr->picture; + if (picture != NULL) { + Blt_PaintPictureWithBlend(imPtr->painter, drawable, picture, + imPtr->pictX, imPtr->pictY, imPtr->width, imPtr->height, + (int)imPtr->anchorPt.x, (int)imPtr->anchorPt.y, 0, 0.4); + } +} + +/* + *--------------------------------------------------------------------------- + * + * ImageToPostscriptProc -- + * + * This procedure is invoked to print a image marker. + * + * Results: + * None. + * + *--------------------------------------------------------------------------- + */ +static void +ImageToPostscriptProc(Marker *markerPtr, Blt_Ps ps) +{ + ImageMarker *imPtr = (ImageMarker *)markerPtr; + Blt_Picture picture; + + picture = (imPtr->scaled != NULL) ? imPtr->scaled : imPtr->picture; + if (picture != NULL) { + Blt_Ps_DrawPicture(ps, picture, imPtr->anchorPt.x, imPtr->anchorPt.y); + } +} + +/* + *--------------------------------------------------------------------------- + * + * FreeImageProc -- + * + * Destroys the structure containing the attributes of the image marker. + * + * Results: + * None. + * + * Side effects: + * Image attributes (GCs, colors, image, etc) get destroyed. Memory is + * released, X resources are freed, and the graph is redrawn. + * + *--------------------------------------------------------------------------- + */ +static void +FreeImageProc(Marker *markerPtr) +{ + ImageMarker *imPtr = (ImageMarker *)markerPtr; + Graph *graphPtr = markerPtr->obj.graphPtr; + + if (imPtr->painter != NULL) { + Blt_FreePainter(imPtr->painter); + } + if (imPtr->scaled != NULL) { + Blt_FreePicture(imPtr->scaled); + } + if (imPtr->gc != NULL) { + Tk_FreeGC(graphPtr->display, imPtr->gc); + } +} + +/* + *--------------------------------------------------------------------------- + * + * CreateImageProc -- + * + * Allocate memory and initialize methods for the new image marker. + * + * Results: + * The pointer to the newly allocated marker structure is returned. + * + * Side effects: + * Memory is allocated for the image marker structure. + * + *--------------------------------------------------------------------------- + */ +static Marker * +CreateImageProc(void) +{ + ImageMarker *imPtr; + + imPtr = Blt_AssertCalloc(1, sizeof(ImageMarker)); + imPtr->classPtr = &imageMarkerClass; + return (Marker *)imPtr; +} + +/* + *--------------------------------------------------------------------------- + * + * ConfigureTextProc -- + * + * This procedure is called to process an objv/objc list, plus the Tk + * option database, in order to configure (or reconfigure) a text marker. + * + * Results: + * A standard TCL result. If TCL_ERROR is returned, then interp->result + * contains an error message. + * + * Side effects: + * Configuration information, such as text string, colors, font, etc. get + * set for markerPtr; old resources get freed, if there were any. The + * marker is eventually redisplayed. + * + *--------------------------------------------------------------------------- + */ +static int +ConfigureTextProc(Marker *markerPtr) +{ + Graph *graphPtr = markerPtr->obj.graphPtr; + TextMarker *tmPtr = (TextMarker *)markerPtr; + GC newGC; + XGCValues gcValues; + unsigned long gcMask; + + tmPtr->style.angle = (float)FMOD(tmPtr->style.angle, 360.0); + if (tmPtr->style.angle < 0.0f) { + tmPtr->style.angle += 360.0f; + } + newGC = NULL; + if (tmPtr->fillColor != NULL) { + gcMask = GCForeground; + gcValues.foreground = tmPtr->fillColor->pixel; + newGC = Tk_GetGC(graphPtr->tkwin, gcMask, &gcValues); + } + if (tmPtr->fillGC != NULL) { + Tk_FreeGC(graphPtr->display, tmPtr->fillGC); + } + tmPtr->fillGC = newGC; + + markerPtr->flags |= MAP_ITEM; + if (markerPtr->drawUnder) { + graphPtr->flags |= CACHE_DIRTY; + } + Blt_EventuallyRedrawGraph(graphPtr); + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * MapTextProc -- + * + * Calculate the layout position for a text marker. Positional information + * is saved in the marker. If the text is rotated, a bitmap containing the + * text is created. + * + * Results: + * None. + * + * Side effects: + * If no background color has been specified, the GC stipple origins are + * changed to current window coordinates. For both rotated and + * non-rotated text, if any old bitmap is leftover, it is freed. + * + *--------------------------------------------------------------------------- + */ +static void +MapTextProc(Marker *markerPtr) +{ + Graph *graphPtr = markerPtr->obj.graphPtr; + TextMarker *tmPtr = (TextMarker *)markerPtr; + Region2d extents; + Point2d anchorPt; + int i; + unsigned int w, h; + double rw, rh; + + tmPtr->width = tmPtr->height = 0; + if (tmPtr->string == NULL) { + return; + } + Blt_Ts_GetExtents(&tmPtr->style, tmPtr->string, &w, &h); + Blt_GetBoundingBox(w, h, tmPtr->style.angle, &rw, &rh, tmPtr->outline); + tmPtr->width = ROUND(rw); + tmPtr->height = ROUND(rh); + for (i = 0; i < 4; i++) { + tmPtr->outline[i].x += ROUND(rw * 0.5); + tmPtr->outline[i].y += ROUND(rh * 0.5); + } + tmPtr->outline[4].x = tmPtr->outline[0].x; + tmPtr->outline[4].y = tmPtr->outline[0].y; + anchorPt = MapPoint(markerPtr->worldPts, &markerPtr->axes); + anchorPt = Blt_AnchorPoint(anchorPt.x, anchorPt.y, (double)(tmPtr->width), + (double)(tmPtr->height), tmPtr->anchor); + anchorPt.x += markerPtr->xOffset; + anchorPt.y += markerPtr->yOffset; + /* + * Determine the bounding box of the text and test to see if it is at + * least partially contained within the plotting area. + */ + extents.left = anchorPt.x; + extents.top = anchorPt.y; + extents.right = anchorPt.x + tmPtr->width - 1; + extents.bottom = anchorPt.y + tmPtr->height - 1; + markerPtr->clipped = BoxesDontOverlap(graphPtr, &extents); + tmPtr->anchorPt = anchorPt; + +} + +static int +PointInTextProc(Marker *markerPtr, Point2d *samplePtr) +{ + TextMarker *tmPtr = (TextMarker *)markerPtr; + + if (tmPtr->string == NULL) { + return 0; + } + if (tmPtr->style.angle != 0.0f) { + Point2d points[5]; + int i; + + /* + * Figure out the bounding polygon (isolateral) for the text and see + * if the point is inside of it. + */ + for (i = 0; i < 5; i++) { + points[i].x = tmPtr->outline[i].x + tmPtr->anchorPt.x; + points[i].y = tmPtr->outline[i].y + tmPtr->anchorPt.y; + } + return Blt_PointInPolygon(samplePtr, points, 5); + } + return ((samplePtr->x >= tmPtr->anchorPt.x) && + (samplePtr->x < (tmPtr->anchorPt.x + tmPtr->width)) && + (samplePtr->y >= tmPtr->anchorPt.y) && + (samplePtr->y < (tmPtr->anchorPt.y + tmPtr->height))); +} + +/* + *--------------------------------------------------------------------------- + * + * RegionInTextProc -- + * + *--------------------------------------------------------------------------- + */ +static int +RegionInTextProc(Marker *markerPtr, Region2d *extsPtr, int enclosed) +{ + TextMarker *tmPtr = (TextMarker *)markerPtr; + + if (markerPtr->nWorldPts < 1) { + return FALSE; + } + if (tmPtr->style.angle != 0.0f) { + Point2d points[5]; + int i; + + /* + * Generate the bounding polygon (isolateral) for the bitmap and see + * if the point is inside of it. + */ + for (i = 0; i < 4; i++) { + points[i].x = tmPtr->outline[i].x + tmPtr->anchorPt.x; + points[i].y = tmPtr->outline[i].y + tmPtr->anchorPt.y; + } + return Blt_RegionInPolygon(extsPtr, points, 4, enclosed); + } + if (enclosed) { + return ((tmPtr->anchorPt.x >= extsPtr->left) && + (tmPtr->anchorPt.y >= extsPtr->top) && + ((tmPtr->anchorPt.x + tmPtr->width) <= extsPtr->right) && + ((tmPtr->anchorPt.y + tmPtr->height) <= extsPtr->bottom)); + } + return !((tmPtr->anchorPt.x >= extsPtr->right) || + (tmPtr->anchorPt.y >= extsPtr->bottom) || + ((tmPtr->anchorPt.x + tmPtr->width) <= extsPtr->left) || + ((tmPtr->anchorPt.y + tmPtr->height) <= extsPtr->top)); +} + +/* + *--------------------------------------------------------------------------- + * + * DrawTextProc -- + * + * Draws the text marker on the graph. + * + * Results: + * None. + * + * Side effects: + * Commands are output to X to draw the marker in its current mode. + * + *--------------------------------------------------------------------------- + */ +static void +DrawTextProc(Marker *markerPtr, Drawable drawable) +{ + TextMarker *tmPtr = (TextMarker *)markerPtr; + Graph *graphPtr = markerPtr->obj.graphPtr; + + if (tmPtr->string == NULL) { + return; + } + if (tmPtr->fillGC != NULL) { + XPoint points[4]; + int i; + + /* + * Simulate the rotated background of the bitmap by filling a bounding + * polygon with the background color. + */ + for (i = 0; i < 4; i++) { + points[i].x = (short int)(tmPtr->outline[i].x + tmPtr->anchorPt.x); + points[i].y = (short int)(tmPtr->outline[i].y + tmPtr->anchorPt.y); + } + XFillPolygon(graphPtr->display, drawable, tmPtr->fillGC, points, 4, + Convex, CoordModeOrigin); + } + if (tmPtr->style.color != NULL) { + Blt_Ts_DrawText(graphPtr->tkwin, drawable, tmPtr->string, -1, + &tmPtr->style, (int)tmPtr->anchorPt.x, (int)tmPtr->anchorPt.y); + } +} + +/* + *--------------------------------------------------------------------------- + * + * TextToPostscriptProc -- + * + * Outputs PostScript commands to draw a text marker at a given x,y + * coordinate, rotation, anchor, and font. + * + * Results: + * None. + * + * Side effects: + * PostScript font and color settings are changed. + * + *--------------------------------------------------------------------------- + */ +static void +TextToPostscriptProc(Marker *markerPtr, Blt_Ps ps) +{ + TextMarker *tmPtr = (TextMarker *)markerPtr; + + if (tmPtr->string == NULL) { + return; + } + if (tmPtr->fillGC != NULL) { + Point2d points[4]; + int i; + + /* + * Simulate the rotated background of the bitmap by filling a bounding + * polygon with the background color. + */ + for (i = 0; i < 4; i++) { + points[i].x = tmPtr->outline[i].x + tmPtr->anchorPt.x; + points[i].y = tmPtr->outline[i].y + tmPtr->anchorPt.y; + } + Blt_Ps_XSetBackground(ps, tmPtr->fillColor); + Blt_Ps_XFillPolygon(ps, points, 4); + } + Blt_Ps_DrawText(ps, tmPtr->string, &tmPtr->style, tmPtr->anchorPt.x, + tmPtr->anchorPt.y); +} + +/* + *--------------------------------------------------------------------------- + * + * FreeTextProc -- + * + * Destroys the structure containing the attributes of the text marker. + * + * Results: + * None. + * + * Side effects: + * Text attributes (GCs, colors, stipple, font, etc) get destroyed. + * Memory is released, X resources are freed, and the graph is redrawn. + * + *--------------------------------------------------------------------------- + */ +static void +FreeTextProc(Marker *markerPtr) +{ + TextMarker *tmPtr = (TextMarker *)markerPtr; + Graph *graphPtr = markerPtr->obj.graphPtr; + + Blt_Ts_FreeStyle(graphPtr->display, &tmPtr->style); +} + +/* + *--------------------------------------------------------------------------- + + * CreateTextProc -- + * + * Allocate memory and initialize methods for the new text marker. + * + * Results: + * The pointer to the newly allocated marker structure is returned. + * + * Side effects: + * Memory is allocated for the text marker structure. + * + *--------------------------------------------------------------------------- + */ +static Marker * +CreateTextProc(void) +{ + TextMarker *tmPtr; + + tmPtr = Blt_AssertCalloc(1, sizeof(TextMarker)); + tmPtr->classPtr = &textMarkerClass; + Blt_Ts_InitStyle(tmPtr->style); + tmPtr->style.anchor = TK_ANCHOR_NW; + tmPtr->style.padLeft = tmPtr->style.padRight = 4; + tmPtr->style.padTop = tmPtr->style.padBottom = 4; + return (Marker *)tmPtr; +} + +static Tk_EventProc ChildEventProc; +static Tk_GeomRequestProc ChildGeometryProc; +static Tk_GeomLostSlaveProc ChildCustodyProc; +static Tk_GeomMgr winMarkerMgrInfo = +{ + (char *)"graph", /* Name of geometry manager used by + * winfo */ + ChildGeometryProc, /* Procedure to for new geometry + * requests. */ + ChildCustodyProc, /* Procedure when window is taken + * away. */ +}; + +/* + *--------------------------------------------------------------------------- + * + * ConfigureWindowProc -- + * + * This procedure is called to process an objv/objc list, plus the Tk + * option database, in order to configure (or reconfigure) a window + * marker. + * + * Results: + * A standard TCL result. If TCL_ERROR is returned, then interp->result + * contains an error message. + * + * Side effects: + * Configuration information, such as window pathname, placement, + * etc. get set for markerPtr; old resources get freed, if there were + * any. The marker is eventually redisplayed. + * + *--------------------------------------------------------------------------- + */ +static int +ConfigureWindowProc(Marker *markerPtr) +{ + Graph *graphPtr = markerPtr->obj.graphPtr; + WindowMarker *wmPtr = (WindowMarker *)markerPtr; + Tk_Window tkwin; + + if (wmPtr->childName == NULL) { + return TCL_OK; + } + tkwin = Tk_NameToWindow(graphPtr->interp, wmPtr->childName, + graphPtr->tkwin); + if (tkwin == NULL) { + return TCL_ERROR; + } + if (Tk_Parent(tkwin) != graphPtr->tkwin) { + Tcl_AppendResult(graphPtr->interp, "\"", wmPtr->childName, + "\" is not a child of \"", Tk_PathName(graphPtr->tkwin), "\"", + (char *)NULL); + return TCL_ERROR; + } + if (tkwin != wmPtr->child) { + if (wmPtr->child != NULL) { + Tk_DeleteEventHandler(wmPtr->child, StructureNotifyMask, + ChildEventProc, wmPtr); + Tk_ManageGeometry(wmPtr->child, (Tk_GeomMgr *) 0, (ClientData)0); + Tk_UnmapWindow(wmPtr->child); + } + Tk_CreateEventHandler(tkwin, StructureNotifyMask, ChildEventProc, + wmPtr); + Tk_ManageGeometry(tkwin, &winMarkerMgrInfo, wmPtr); + } + wmPtr->child = tkwin; + markerPtr->flags |= MAP_ITEM; + if (markerPtr->drawUnder) { + graphPtr->flags |= CACHE_DIRTY; + } + Blt_EventuallyRedrawGraph(graphPtr); + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * MapWindowProc -- + * + * Calculate the layout position for a window marker. Positional + * information is saved in the marker. + * + * Results: + * None. + * + *--------------------------------------------------------------------------- + */ +static void +MapWindowProc(Marker *markerPtr) +{ + WindowMarker *wmPtr = (WindowMarker *)markerPtr; + Graph *graphPtr = markerPtr->obj.graphPtr; + Point2d anchorPt; + Region2d extents; + int width, height; + + if (wmPtr->child == (Tk_Window)NULL) { + return; + } + anchorPt = MapPoint(markerPtr->worldPts, &markerPtr->axes); + + width = Tk_ReqWidth(wmPtr->child); + height = Tk_ReqHeight(wmPtr->child); + if (wmPtr->reqWidth > 0) { + width = wmPtr->reqWidth; + } + if (wmPtr->reqHeight > 0) { + height = wmPtr->reqHeight; + } + wmPtr->anchorPt = Blt_AnchorPoint(anchorPt.x, anchorPt.y, (double)width, + (double)height, wmPtr->anchor); + wmPtr->anchorPt.x += markerPtr->xOffset; + wmPtr->anchorPt.y += markerPtr->yOffset; + wmPtr->width = width; + wmPtr->height = height; + + /* + * Determine the bounding box of the window and test to see if it is at + * least partially contained within the plotting area. + */ + extents.left = wmPtr->anchorPt.x; + extents.top = wmPtr->anchorPt.y; + extents.right = wmPtr->anchorPt.x + wmPtr->width - 1; + extents.bottom = wmPtr->anchorPt.y + wmPtr->height - 1; + markerPtr->clipped = BoxesDontOverlap(graphPtr, &extents); +} + +/* + *--------------------------------------------------------------------------- + * + * PointInWindowProc -- + * + *--------------------------------------------------------------------------- + */ +static int +PointInWindowProc(Marker *markerPtr, Point2d *samplePtr) +{ + WindowMarker *wmPtr = (WindowMarker *)markerPtr; + + return ((samplePtr->x >= wmPtr->anchorPt.x) && + (samplePtr->x < (wmPtr->anchorPt.x + wmPtr->width)) && + (samplePtr->y >= wmPtr->anchorPt.y) && + (samplePtr->y < (wmPtr->anchorPt.y + wmPtr->height))); +} + +/* + *--------------------------------------------------------------------------- + * + * RegionInWindowProc -- + * + *--------------------------------------------------------------------------- + */ +static int +RegionInWindowProc(Marker *markerPtr, Region2d *extsPtr, int enclosed) +{ + WindowMarker *wmPtr = (WindowMarker *)markerPtr; + + if (markerPtr->nWorldPts < 1) { + return FALSE; + } + if (enclosed) { + return ((wmPtr->anchorPt.x >= extsPtr->left) && + (wmPtr->anchorPt.y >= extsPtr->top) && + ((wmPtr->anchorPt.x + wmPtr->width) <= extsPtr->right) && + ((wmPtr->anchorPt.y + wmPtr->height) <= extsPtr->bottom)); + } + return !((wmPtr->anchorPt.x >= extsPtr->right) || + (wmPtr->anchorPt.y >= extsPtr->bottom) || + ((wmPtr->anchorPt.x + wmPtr->width) <= extsPtr->left) || + ((wmPtr->anchorPt.y + wmPtr->height) <= extsPtr->top)); +} + +/* + *--------------------------------------------------------------------------- + * + * DrawWindowProc -- + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static void +DrawWindowProc(Marker *markerPtr, Drawable drawable) +{ + WindowMarker *wmPtr = (WindowMarker *)markerPtr; + + if (wmPtr->child == NULL) { + return; + } + if ((wmPtr->height != Tk_Height(wmPtr->child)) || + (wmPtr->width != Tk_Width(wmPtr->child)) || + ((int)wmPtr->anchorPt.x != Tk_X(wmPtr->child)) || + ((int)wmPtr->anchorPt.y != Tk_Y(wmPtr->child))) { + Tk_MoveResizeWindow(wmPtr->child, (int)wmPtr->anchorPt.x, + (int)wmPtr->anchorPt.y, wmPtr->width, wmPtr->height); + } + if (!Tk_IsMapped(wmPtr->child)) { + Tk_MapWindow(wmPtr->child); + } +} + +/* + *--------------------------------------------------------------------------- + * + * WindowToPostscriptProc -- + * + *--------------------------------------------------------------------------- + */ +static void +WindowToPostscriptProc(Marker *markerPtr, Blt_Ps ps) +{ + WindowMarker *wmPtr = (WindowMarker *)markerPtr; + + if (wmPtr->child == NULL) { + return; + } + if (Tk_IsMapped(wmPtr->child)) { + Blt_Ps_XDrawWindow(ps, wmPtr->child, wmPtr->anchorPt.x, + wmPtr->anchorPt.y); + } +} + +/* + *--------------------------------------------------------------------------- + * + * FreeWindowProc -- + * + * Destroys the structure containing the attributes of the window * + * marker. + * + * Results: + * None. + * + * Side effects: + * Window is destroyed and removed from the screen. + * + *--------------------------------------------------------------------------- + */ +static void +FreeWindowProc(Marker *markerPtr) +{ + WindowMarker *wmPtr = (WindowMarker *)markerPtr; + + if (wmPtr->child != NULL) { + Tk_DeleteEventHandler(wmPtr->child, StructureNotifyMask, + ChildEventProc, wmPtr); + Tk_ManageGeometry(wmPtr->child, (Tk_GeomMgr *) 0, (ClientData)0); + Tk_DestroyWindow(wmPtr->child); + } +} + +/* + *--------------------------------------------------------------------------- + * + * CreateWindowProc -- + * + * Allocate memory and initialize methods for the new window marker. + * + * Results: + * The pointer to the newly allocated marker structure is returned. + * + * Side effects: + * Memory is allocated for the window marker structure. + * + *--------------------------------------------------------------------------- + */ +static Marker * +CreateWindowProc(void) +{ + WindowMarker *wmPtr; + + wmPtr = Blt_AssertCalloc(1, sizeof(WindowMarker)); + wmPtr->classPtr = &windowMarkerClass; + return (Marker *)wmPtr; +} + +/* + *--------------------------------------------------------------------------- + * + * ChildEventProc -- + * + * This procedure is invoked whenever StructureNotify events occur for a + * window that's managed as part of a graph window marker. This + * procedure's only purpose is to clean up when windows are deleted. + * + * Results: + * None. + * + * Side effects: + * The window is disassociated from the window item when it is + * deleted. + * + *--------------------------------------------------------------------------- + */ +static void +ChildEventProc(ClientData clientData, XEvent *eventPtr) +{ + WindowMarker *wmPtr = clientData; + + if (eventPtr->type == DestroyNotify) { + wmPtr->child = NULL; + } +} + +/* + *--------------------------------------------------------------------------- + * + * ChildGeometryProc -- + * + * This procedure is invoked whenever a window that's associated with a + * window item changes its requested dimensions. + * + * Results: + * None. + * + * Side effects: + * The size and location on the window of the window may change, + * depending on the options specified for the window item. + * + *--------------------------------------------------------------------------- + */ +/* ARGSUSED */ +static void +ChildGeometryProc(ClientData clientData, Tk_Window tkwin) +{ + WindowMarker *wmPtr = clientData; + + if (wmPtr->reqWidth == 0) { + wmPtr->width = Tk_ReqWidth(tkwin); + } + if (wmPtr->reqHeight == 0) { + wmPtr->height = Tk_ReqHeight(tkwin); + } +} + +/* + *--------------------------------------------------------------------------- + * + * ChildCustodyProc -- + * + * This procedure is invoked when an embedded window has been stolen by + * another geometry manager. The information and memory associated with + * the widget is released. + * + * Results: + * None. + * + * Side effects: + * Arranges for the graph to be redrawn without the embedded widget at + * the next idle point. + * + *--------------------------------------------------------------------------- + */ + /* ARGSUSED */ +static void +ChildCustodyProc(ClientData clientData, Tk_Window tkwin) +{ + Marker *markerPtr = clientData; + Graph *graphPtr; + + graphPtr = markerPtr->obj.graphPtr; + markerPtr->flags |= DELETE_PENDING; + Tcl_EventuallyFree(markerPtr, FreeMarker); + /* + * Not really needed. We should get an Expose event when the child window + * is unmapped. + */ + Blt_EventuallyRedrawGraph(graphPtr); +} + +/* + *--------------------------------------------------------------------------- + * + * MapLineProc -- + * + * Calculate the layout position for a line marker. Positional + * information is saved in the marker. The line positions are stored in + * an array of points (malloc'ed). + * + * Results: + * None. + * + *--------------------------------------------------------------------------- + */ +static void +MapLineProc(Marker *markerPtr) +{ + Graph *graphPtr = markerPtr->obj.graphPtr; + LineMarker *lmPtr = (LineMarker *)markerPtr; + Point2d *srcPtr, *pend; + Segment2d *segments, *segPtr; + Point2d p, q; + Region2d extents; + + lmPtr->nSegments = 0; + if (lmPtr->segments != NULL) { + Blt_Free(lmPtr->segments); + } + if (markerPtr->nWorldPts < 2) { + return; /* Too few points */ + } + Blt_GraphExtents(graphPtr, &extents); + + /* + * Allow twice the number of world coordinates. The line will represented + * as series of line segments, not one continous polyline. This is + * because clipping against the plot area may chop the line into several + * disconnected segments. + */ + segments = Blt_AssertMalloc(markerPtr->nWorldPts * sizeof(Segment2d)); + srcPtr = markerPtr->worldPts; + p = MapPoint(srcPtr, &markerPtr->axes); + p.x += markerPtr->xOffset; + p.y += markerPtr->yOffset; + + segPtr = segments; + for (srcPtr++, pend = markerPtr->worldPts + markerPtr->nWorldPts; + srcPtr < pend; srcPtr++) { + Point2d next; + + next = MapPoint(srcPtr, &markerPtr->axes); + next.x += markerPtr->xOffset; + next.y += markerPtr->yOffset; + q = next; + if (Blt_LineRectClip(&extents, &p, &q)) { + segPtr->p = p; + segPtr->q = q; + segPtr++; + } + p = next; + } + lmPtr->nSegments = segPtr - segments; + lmPtr->segments = segments; + markerPtr->clipped = (lmPtr->nSegments == 0); +} + +static int +PointInLineProc(Marker *markerPtr, Point2d *samplePtr) +{ + LineMarker *lmPtr = (LineMarker *)markerPtr; + + return Blt_PointInSegments(samplePtr, lmPtr->segments, lmPtr->nSegments, + (double)markerPtr->obj.graphPtr->halo); +} + +/* + *--------------------------------------------------------------------------- + * + * RegionInLineProc -- + * + *--------------------------------------------------------------------------- + */ +static int +RegionInLineProc(Marker *markerPtr, Region2d *extsPtr, int enclosed) +{ + if (markerPtr->nWorldPts < 2) { + return FALSE; + } + if (enclosed) { + Point2d *pp, *pend; + + for (pp = markerPtr->worldPts, pend = pp + markerPtr->nWorldPts; + pp < pend; pp++) { + Point2d p; + + p = MapPoint(pp, &markerPtr->axes); + if ((p.x < extsPtr->left) && (p.x > extsPtr->right) && + (p.y < extsPtr->top) && (p.y > extsPtr->bottom)) { + return FALSE; + } + } + return TRUE; /* All points inside bounding box. */ + } else { + int count; + Point2d *pp, *pend; + + count = 0; + for (pp = markerPtr->worldPts, pend = pp + (markerPtr->nWorldPts - 1); + pp < pend; pp++) { + Point2d p, q; + + p = MapPoint(pp, &markerPtr->axes); + q = MapPoint(pp + 1, &markerPtr->axes); + if (Blt_LineRectClip(extsPtr, &p, &q)) { + count++; + } + } + return (count > 0); /* At least 1 segment passes through + * region. */ + } +} + +/* + *--------------------------------------------------------------------------- + * + * DrawLineProc -- + * + *--------------------------------------------------------------------------- + */ +static void +DrawLineProc(Marker *markerPtr, Drawable drawable) +{ + LineMarker *lmPtr = (LineMarker *)markerPtr; + + if (lmPtr->nSegments > 0) { + Graph *graphPtr = markerPtr->obj.graphPtr; + + Blt_Draw2DSegments(graphPtr->display, drawable, lmPtr->gc, + lmPtr->segments, lmPtr->nSegments); + if (lmPtr->xor) { /* Toggle the drawing state */ + lmPtr->xorState = (lmPtr->xorState == 0); + } + } +} + +/* + *--------------------------------------------------------------------------- + * + * ConfigureLineProc -- + * + * This procedure is called to process an objv/objc list, plus the Tk + * option database, in order to configure (or reconfigure) a line marker. + * + * Results: + * A standard TCL result. If TCL_ERROR is returned, then interp->result + * contains an error message. + * + * Side effects: + * Configuration information, such as line width, colors, dashes, + * etc. get set for markerPtr; old resources get freed, if there were + * any. The marker is eventually redisplayed. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +ConfigureLineProc(Marker *markerPtr) +{ + Graph *graphPtr = markerPtr->obj.graphPtr; + LineMarker *lmPtr = (LineMarker *)markerPtr; + GC newGC; + XGCValues gcValues; + unsigned long gcMask; + Drawable drawable; + + drawable = Tk_WindowId(graphPtr->tkwin); + gcMask = (GCLineWidth | GCLineStyle | GCCapStyle | GCJoinStyle); + if (lmPtr->outlineColor != NULL) { + gcMask |= GCForeground; + gcValues.foreground = lmPtr->outlineColor->pixel; + } + if (lmPtr->fillColor != NULL) { + gcMask |= GCBackground; + gcValues.background = lmPtr->fillColor->pixel; + } + gcValues.cap_style = lmPtr->capStyle; + gcValues.join_style = lmPtr->joinStyle; + gcValues.line_width = LineWidth(lmPtr->lineWidth); + gcValues.line_style = LineSolid; + if (LineIsDashed(lmPtr->dashes)) { + gcValues.line_style = + (gcMask & GCBackground) ? LineDoubleDash : LineOnOffDash; + } + if (lmPtr->xor) { + unsigned long pixel; + gcValues.function = GXxor; + + gcMask |= GCFunction; + if (graphPtr->plotBg == NULL) { + pixel = WhitePixelOfScreen(Tk_Screen(graphPtr->tkwin)); + } else { + pixel = Blt_BackgroundBorderColor(graphPtr->plotBg)->pixel; + } + if (gcMask & GCBackground) { + gcValues.background ^= pixel; + } + gcValues.foreground ^= pixel; + if (drawable != None) { + DrawLineProc(markerPtr, drawable); + } + } + newGC = Blt_GetPrivateGC(graphPtr->tkwin, gcMask, &gcValues); + if (lmPtr->gc != NULL) { + Blt_FreePrivateGC(graphPtr->display, lmPtr->gc); + } + if (LineIsDashed(lmPtr->dashes)) { + Blt_SetDashes(graphPtr->display, newGC, &lmPtr->dashes); + } + lmPtr->gc = newGC; + if (lmPtr->xor) { + if (drawable != None) { + MapLineProc(markerPtr); + DrawLineProc(markerPtr, drawable); + } + return TCL_OK; + } + markerPtr->flags |= MAP_ITEM; + if (markerPtr->drawUnder) { + graphPtr->flags |= CACHE_DIRTY; + } + Blt_EventuallyRedrawGraph(graphPtr); + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * LineToPostscriptProc -- + * + * Prints postscript commands to display the connect line. Dashed lines + * need to be handled specially, especially if a background color is + * designated. + * + * Results: + * None. + * + * Side effects: + * PostScript output commands are saved in the interpreter + * (infoPtr->interp) result field. + * + *--------------------------------------------------------------------------- + */ +static void +LineToPostscriptProc(Marker *markerPtr, Blt_Ps ps) +{ + LineMarker *lmPtr = (LineMarker *)markerPtr; + + if (lmPtr->nSegments > 0) { + Blt_Ps_XSetLineAttributes(ps, lmPtr->outlineColor, + lmPtr->lineWidth, &lmPtr->dashes, lmPtr->capStyle, + lmPtr->joinStyle); + if ((LineIsDashed(lmPtr->dashes)) && (lmPtr->fillColor != NULL)) { + Blt_Ps_Append(ps, "/DashesProc {\n gsave\n "); + Blt_Ps_XSetBackground(ps, lmPtr->fillColor); + Blt_Ps_Append(ps, " "); + Blt_Ps_XSetDashes(ps, (Blt_Dashes *)NULL); + Blt_Ps_VarAppend(ps, + "stroke\n", + " grestore\n", + "} def\n", (char *)NULL); + } else { + Blt_Ps_Append(ps, "/DashesProc {} def\n"); + } + Blt_Ps_Draw2DSegments(ps, lmPtr->segments, lmPtr->nSegments); + } +} + +/* + *--------------------------------------------------------------------------- + * + * FreeLineProc -- + * + * Destroys the structure and attributes of a line marker. + * + * Results: + * None. + * + * Side effects: + * Line attributes (GCs, colors, stipple, etc) get released. Memory is + * deallocated, X resources are freed. + * + *--------------------------------------------------------------------------- + */ +static void +FreeLineProc(Marker *markerPtr) +{ + LineMarker *lmPtr = (LineMarker *)markerPtr; + Graph *graphPtr = markerPtr->obj.graphPtr; + + if (lmPtr->gc != NULL) { + Blt_FreePrivateGC(graphPtr->display, lmPtr->gc); + } + if (lmPtr->segments != NULL) { + Blt_Free(lmPtr->segments); + } +} + + +/* + *--------------------------------------------------------------------------- + * + * CreateLineProc -- + * + * Allocate memory and initialize methods for a new line marker. + * + * Results: + * The pointer to the newly allocated marker structure is returned. + * + * Side effects: + * Memory is allocated for the line marker structure. + * + *--------------------------------------------------------------------------- + */ +static Marker * +CreateLineProc(void) +{ + LineMarker *lmPtr; + + lmPtr = Blt_AssertCalloc(1, sizeof(LineMarker)); + lmPtr->classPtr = &lineMarkerClass; + lmPtr->xor = FALSE; + lmPtr->capStyle = CapButt; + lmPtr->joinStyle = JoinMiter; + return (Marker *)lmPtr; +} + +/* + *--------------------------------------------------------------------------- + * + * MapPolygonProc -- + * + * Calculate the layout position for a polygon marker. Positional + * information is saved in the polygon in an array of points (malloc'ed). + * + * Results: + * None. + * + *--------------------------------------------------------------------------- + */ +static void +MapPolygonProc(Marker *markerPtr) +{ + Graph *graphPtr = markerPtr->obj.graphPtr; + PolygonMarker *pmPtr = (PolygonMarker *)markerPtr; + Point2d *screenPts; + Region2d extents; + int nScreenPts; + + if (pmPtr->outlinePts != NULL) { + Blt_Free(pmPtr->outlinePts); + pmPtr->outlinePts = NULL; + pmPtr->nOutlinePts = 0; + } + if (pmPtr->fillPts != NULL) { + Blt_Free(pmPtr->fillPts); + pmPtr->fillPts = NULL; + pmPtr->nFillPts = 0; + } + if (pmPtr->screenPts != NULL) { + Blt_Free(pmPtr->screenPts); + pmPtr->screenPts = NULL; + } + if (markerPtr->nWorldPts < 3) { + return; /* Too few points */ + } + + /* + * Allocate and fill a temporary array to hold the screen coordinates of + * the polygon. + */ + nScreenPts = markerPtr->nWorldPts + 1; + screenPts = Blt_AssertMalloc((nScreenPts + 1) * sizeof(Point2d)); + { + Point2d *sp, *dp, *send; + + dp = screenPts; + for (sp = markerPtr->worldPts, send = sp + markerPtr->nWorldPts; + sp < send; sp++) { + *dp = MapPoint(sp, &markerPtr->axes); + dp->x += markerPtr->xOffset; + dp->y += markerPtr->yOffset; + dp++; + } + *dp = screenPts[0]; + } + Blt_GraphExtents(graphPtr, &extents); + markerPtr->clipped = TRUE; + if (pmPtr->fill.fgColor != NULL) { /* Polygon fill required. */ + Point2d *fillPts; + int n; + + fillPts = Blt_AssertMalloc(sizeof(Point2d) * nScreenPts * 3); + n = Blt_PolyRectClip(&extents, screenPts, markerPtr->nWorldPts,fillPts); + if (n < 3) { + Blt_Free(fillPts); + } else { + pmPtr->nFillPts = n; + pmPtr->fillPts = fillPts; + markerPtr->clipped = FALSE; + } + } + if ((pmPtr->outline.fgColor != NULL) && (pmPtr->lineWidth > 0)) { + Segment2d *outlinePts; + Segment2d *segPtr; + Point2d *sp, *send; + + /* + * Generate line segments representing the polygon outline. The + * resulting outline may or may not be closed from viewport clipping. + */ + outlinePts = Blt_Malloc(nScreenPts * sizeof(Segment2d)); + if (outlinePts == NULL) { + return; /* Can't allocate point array */ + } + /* + * Note that this assumes that the point array contains an extra point + * that closes the polygon. + */ + segPtr = outlinePts; + for (sp = screenPts, send = sp + (nScreenPts - 1); sp < send; sp++) { + segPtr->p = sp[0]; + segPtr->q = sp[1]; + if (Blt_LineRectClip(&extents, &segPtr->p, &segPtr->q)) { + segPtr++; + } + } + pmPtr->nOutlinePts = segPtr - outlinePts; + pmPtr->outlinePts = outlinePts; + if (pmPtr->nOutlinePts > 0) { + markerPtr->clipped = FALSE; + } + } + pmPtr->screenPts = screenPts; +} + +static int +PointInPolygonProc(Marker *markerPtr, Point2d *samplePtr) +{ + PolygonMarker *pmPtr = (PolygonMarker *)markerPtr; + + if ((markerPtr->nWorldPts >= 3) && (pmPtr->screenPts != NULL)) { + return Blt_PointInPolygon(samplePtr, pmPtr->screenPts, + markerPtr->nWorldPts + 1); + } + return FALSE; +} + +/* + *--------------------------------------------------------------------------- + * + * RegionInPolygonProc -- + * + *--------------------------------------------------------------------------- + */ +static int +RegionInPolygonProc(Marker *markerPtr, Region2d *extsPtr, int enclosed) +{ + PolygonMarker *pmPtr = (PolygonMarker *)markerPtr; + + if ((markerPtr->nWorldPts >= 3) && (pmPtr->screenPts != NULL)) { + return Blt_RegionInPolygon(extsPtr, pmPtr->screenPts, + markerPtr->nWorldPts, enclosed); + } + return FALSE; +} + +static void +DrawPolygonProc(Marker *markerPtr, Drawable drawable) +{ + Graph *graphPtr = markerPtr->obj.graphPtr; + PolygonMarker *pmPtr = (PolygonMarker *)markerPtr; + + /* Draw polygon fill region */ + if ((pmPtr->nFillPts > 0) && (pmPtr->fill.fgColor != NULL)) { + XPoint *dp, *points; + Point2d *sp, *send; + + points = Blt_Malloc(pmPtr->nFillPts * sizeof(XPoint)); + if (points == NULL) { + return; + } + dp = points; + for (sp = pmPtr->fillPts, send = sp + pmPtr->nFillPts; sp < send; + sp++) { + dp->x = (short int)sp->x; + dp->y = (short int)sp->y; + dp++; + } + + XFillPolygon(graphPtr->display, drawable, pmPtr->fillGC, points, + pmPtr->nFillPts, Complex, CoordModeOrigin); + Blt_Free(points); + } + /* and then the outline */ + if ((pmPtr->nOutlinePts > 0) && (pmPtr->lineWidth > 0) && + (pmPtr->outline.fgColor != NULL)) { + Blt_Draw2DSegments(graphPtr->display, drawable, pmPtr->outlineGC, + pmPtr->outlinePts, pmPtr->nOutlinePts); + } +} + + +static void +PolygonToPostscriptProc(Marker *markerPtr, Blt_Ps ps) +{ + Graph *graphPtr = markerPtr->obj.graphPtr; + PolygonMarker *pmPtr = (PolygonMarker *)markerPtr; + + if (pmPtr->fill.fgColor != NULL) { + + /* + * Options: fg bg + * Draw outline only. + * x Draw solid or stipple. + * x x Draw solid or stipple. + */ + + /* Create a path to use for both the polygon and its outline. */ + Blt_Ps_Polyline(ps, pmPtr->fillPts, pmPtr->nFillPts); + + /* If the background fill color was specified, draw the polygon in a + * solid fashion with that color. */ + if (pmPtr->fill.bgColor != NULL) { + /* Draw the solid background as the background layer of the opaque + * stipple */ + Blt_Ps_XSetBackground(ps, pmPtr->fill.bgColor); + /* Retain the path. We'll need it for the foreground layer. */ + Blt_Ps_Append(ps, "gsave fill grestore\n"); + } + Blt_Ps_XSetForeground(ps, pmPtr->fill.fgColor); + if (pmPtr->stipple != None) { + /* Draw the stipple in the foreground color. */ + Blt_Ps_XSetStipple(ps, graphPtr->display, pmPtr->stipple); + } else { + Blt_Ps_Append(ps, "fill\n"); + } + } + + /* Draw the outline in the foreground color. */ + if ((pmPtr->lineWidth > 0) && (pmPtr->outline.fgColor != NULL)) { + + /* Set up the line attributes. */ + Blt_Ps_XSetLineAttributes(ps, pmPtr->outline.fgColor, + pmPtr->lineWidth, &pmPtr->dashes, pmPtr->capStyle, + pmPtr->joinStyle); + + /* + * Define on-the-fly a PostScript macro "DashesProc" that will be + * executed for each call to the Polygon drawing routine. If the line + * isn't dashed, simply make this an empty definition. + */ + if ((pmPtr->outline.bgColor != NULL) && (LineIsDashed(pmPtr->dashes))) { + Blt_Ps_Append(ps, "/DashesProc {\ngsave\n "); + Blt_Ps_XSetBackground(ps, pmPtr->outline.bgColor); + Blt_Ps_Append(ps, " "); + Blt_Ps_XSetDashes(ps, (Blt_Dashes *)NULL); + Blt_Ps_Append(ps, "stroke\n grestore\n} def\n"); + } else { + Blt_Ps_Append(ps, "/DashesProc {} def\n"); + } + Blt_Ps_Draw2DSegments(ps, pmPtr->outlinePts, pmPtr->nOutlinePts); + } +} + +/* + *--------------------------------------------------------------------------- + * + * ConfigurePolygonProc -- + * + * This procedure is called to process an objv/objc list, plus the Tk + * option database, in order to configure (or reconfigure) a polygon + * marker. + * + * Results: + * A standard TCL result. If TCL_ERROR is returned, then interp->result + * contains an error message. + * + * Side effects: + * Configuration information, such as polygon color, dashes, fillstyle, + * etc. get set for markerPtr; old resources get freed, if there were + * any. The marker is eventually redisplayed. + * + * -------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +ConfigurePolygonProc(Marker *markerPtr) +{ + Graph *graphPtr = markerPtr->obj.graphPtr; + PolygonMarker *pmPtr = (PolygonMarker *)markerPtr; + GC newGC; + XGCValues gcValues; + unsigned long gcMask; + Drawable drawable; + + drawable = Tk_WindowId(graphPtr->tkwin); + gcMask = (GCLineWidth | GCLineStyle); + if (pmPtr->outline.fgColor != NULL) { + gcMask |= GCForeground; + gcValues.foreground = pmPtr->outline.fgColor->pixel; + } + if (pmPtr->outline.bgColor != NULL) { + gcMask |= GCBackground; + gcValues.background = pmPtr->outline.bgColor->pixel; + } + gcMask |= (GCCapStyle | GCJoinStyle); + gcValues.cap_style = pmPtr->capStyle; + gcValues.join_style = pmPtr->joinStyle; + gcValues.line_style = LineSolid; + gcValues.dash_offset = 0; + gcValues.line_width = LineWidth(pmPtr->lineWidth); + if (LineIsDashed(pmPtr->dashes)) { + gcValues.line_style = (pmPtr->outline.bgColor == NULL) + ? LineOnOffDash : LineDoubleDash; + } + if (pmPtr->xor) { + unsigned long pixel; + gcValues.function = GXxor; + + gcMask |= GCFunction; + if (graphPtr->plotBg == NULL) { + /* The graph's color option may not have been set yet */ + pixel = WhitePixelOfScreen(Tk_Screen(graphPtr->tkwin)); + } else { + pixel = Blt_BackgroundBorderColor(graphPtr->plotBg)->pixel; + } + if (gcMask & GCBackground) { + gcValues.background ^= pixel; + } + gcValues.foreground ^= pixel; + if (drawable != None) { + DrawPolygonProc(markerPtr, drawable); + } + } + newGC = Blt_GetPrivateGC(graphPtr->tkwin, gcMask, &gcValues); + if (LineIsDashed(pmPtr->dashes)) { + Blt_SetDashes(graphPtr->display, newGC, &pmPtr->dashes); + } + if (pmPtr->outlineGC != NULL) { + Blt_FreePrivateGC(graphPtr->display, pmPtr->outlineGC); + } + pmPtr->outlineGC = newGC; + + gcMask = 0; + if (pmPtr->fill.fgColor != NULL) { + gcMask |= GCForeground; + gcValues.foreground = pmPtr->fill.fgColor->pixel; + } + if (pmPtr->fill.bgColor != NULL) { + gcMask |= GCBackground; + gcValues.background = pmPtr->fill.bgColor->pixel; + } + if (pmPtr->stipple != None) { + gcValues.stipple = pmPtr->stipple; + gcValues.fill_style = (pmPtr->fill.bgColor != NULL) + ? FillOpaqueStippled : FillStippled; + gcMask |= (GCStipple | GCFillStyle); + } + newGC = Tk_GetGC(graphPtr->tkwin, gcMask, &gcValues); + if (pmPtr->fillGC != NULL) { + Tk_FreeGC(graphPtr->display, pmPtr->fillGC); + } + pmPtr->fillGC = newGC; + + if ((gcMask == 0) && !(graphPtr->flags & RESET_AXES) && (pmPtr->xor)) { + if (drawable != None) { + MapPolygonProc(markerPtr); + DrawPolygonProc(markerPtr, drawable); + } + return TCL_OK; + } + markerPtr->flags |= MAP_ITEM; + if (markerPtr->drawUnder) { + graphPtr->flags |= CACHE_DIRTY; + } + graphPtr->flags |= RESET_WORLD; + Blt_EventuallyRedrawGraph(graphPtr); + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * FreePolygonProc -- + * + * Release memory and resources allocated for the polygon element. + * + * Results: + * None. + * + * Side effects: + * Everything associated with the polygon element is freed up. + * + *--------------------------------------------------------------------------- + */ +static void +FreePolygonProc(Marker *markerPtr) +{ + PolygonMarker *pmPtr = (PolygonMarker *)markerPtr; + Graph *graphPtr = markerPtr->obj.graphPtr; + + if (pmPtr->fillGC != NULL) { + Tk_FreeGC(graphPtr->display, pmPtr->fillGC); + } + if (pmPtr->outlineGC != NULL) { + Blt_FreePrivateGC(graphPtr->display, pmPtr->outlineGC); + } + if (pmPtr->fillPts != NULL) { + Blt_Free(pmPtr->fillPts); + } + if (pmPtr->outlinePts != NULL) { + Blt_Free(pmPtr->outlinePts); + } + if (pmPtr->screenPts != NULL) { + Blt_Free(pmPtr->screenPts); + } +} + +/* + *--------------------------------------------------------------------------- + * + * CreatePolygonProc -- + * + * Allocate memory and initialize methods for the new polygon marker. + * + * Results: + * The pointer to the newly allocated marker structure is returned. + * + * Side effects: + * Memory is allocated for the polygon marker structure. + * + * -------------------------------------------------------------------------- + */ +static Marker * +CreatePolygonProc(void) +{ + PolygonMarker *pmPtr; + + pmPtr = Blt_AssertCalloc(1, sizeof(PolygonMarker)); + pmPtr->classPtr = &polygonMarkerClass; + pmPtr->capStyle = CapButt; + pmPtr->joinStyle = JoinMiter; + return (Marker *)pmPtr; +} + +static int +GetMarkerFromObj(Tcl_Interp *interp, Graph *graphPtr, Tcl_Obj *objPtr, + Marker **markerPtrPtr) +{ + Blt_HashEntry *hPtr; + const char *string; + + string = Tcl_GetString(objPtr); + hPtr = Blt_FindHashEntry(&graphPtr->markers.table, string); + if (hPtr != NULL) { + *markerPtrPtr = Blt_GetHashValue(hPtr); + return TCL_OK; + } + if (interp != NULL) { + Tcl_AppendResult(interp, "can't find marker \"", string, + "\" in \"", Tk_PathName(graphPtr->tkwin), (char *)NULL); + } + return TCL_ERROR; +} + + +static int +RenameMarker(Graph *graphPtr, Marker *markerPtr, const char *oldName, + const char *newName) +{ + int isNew; + Blt_HashEntry *hPtr; + + /* Rename the marker only if no marker already exists by that name */ + hPtr = Blt_CreateHashEntry(&graphPtr->markers.table, newName, &isNew); + if (!isNew) { + Tcl_AppendResult(graphPtr->interp, "can't rename marker: \"", newName, + "\" already exists", (char *)NULL); + return TCL_ERROR; + } + markerPtr->obj.name = Blt_AssertStrdup(newName); + markerPtr->hashPtr = hPtr; + Blt_SetHashValue(hPtr, (char *)markerPtr); + + /* Delete the old hash entry */ + hPtr = Blt_FindHashEntry(&graphPtr->markers.table, oldName); + Blt_DeleteHashEntry(&graphPtr->markers.table, hPtr); + if (oldName != NULL) { + Blt_Free(oldName); + } + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * NamesOp -- + * + * Returns a list of marker identifiers in interp->result; + * + * Results: + * The return value is a standard TCL result. + * + *--------------------------------------------------------------------------- + */ +static int +NamesOp(Graph *graphPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) +{ + Tcl_Obj *listObjPtr; + + listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL); + if (objc == 3) { + Blt_ChainLink link; + + for (link = Blt_Chain_FirstLink(graphPtr->markers.displayList); + link != NULL; link = Blt_Chain_NextLink(link)) { + Marker *markerPtr; + + markerPtr = Blt_Chain_GetValue(link); + Tcl_ListObjAppendElement(interp, listObjPtr, + Tcl_NewStringObj(markerPtr->obj.name, -1)); + } + } else { + Blt_ChainLink link; + + for (link = Blt_Chain_FirstLink(graphPtr->markers.displayList); + link != NULL; link = Blt_Chain_NextLink(link)) { + Marker *markerPtr; + int i; + + markerPtr = Blt_Chain_GetValue(link); + for (i = 3; i < objc; i++) { + const char *pattern; + + pattern = Tcl_GetString(objv[i]); + if (Tcl_StringMatch(markerPtr->obj.name, pattern)) { + Tcl_ListObjAppendElement(interp, listObjPtr, + Tcl_NewStringObj(markerPtr->obj.name, -1)); + break; + } + } + } + } + Tcl_SetObjResult(interp, listObjPtr); + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * BindOp -- + * + * .g element bind elemName sequence command + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +BindOp(Graph *graphPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) +{ + if (objc == 3) { + Blt_HashEntry *hp; + Blt_HashSearch iter; + Tcl_Obj *listObjPtr; + + listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL); + for (hp = Blt_FirstHashEntry(&graphPtr->markers.tagTable, &iter); + hp != NULL; hp = Blt_NextHashEntry(&iter)) { + const char *tag; + Tcl_Obj *objPtr; + + tag = Blt_GetHashKey(&graphPtr->markers.tagTable, hp); + objPtr = Tcl_NewStringObj(tag, -1); + Tcl_ListObjAppendElement(interp, listObjPtr, objPtr); + } + Tcl_SetObjResult(interp, listObjPtr); + return TCL_OK; + } + return Blt_ConfigureBindingsFromObj(interp, graphPtr->bindTable, + Blt_MakeMarkerTag(graphPtr, Tcl_GetString(objv[3])), + objc - 4, objv + 4); +} + +/* + *--------------------------------------------------------------------------- + * + * CgetOp -- + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +CgetOp(Graph *graphPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) +{ + Marker *markerPtr; + + if (GetMarkerFromObj(interp, graphPtr, objv[3], &markerPtr) != TCL_OK) { + return TCL_ERROR; + } + if (Blt_ConfigureValueFromObj(interp, graphPtr->tkwin, + markerPtr->classPtr->configSpecs, (char *)markerPtr, objv[4], 0) + != TCL_OK) { + return TCL_ERROR; + } + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * ConfigureOp -- + * + * Results: + * The return value is a standard TCL result. + * + * Side Effects: + * + *--------------------------------------------------------------------------- + */ +static int +ConfigureOp(Graph *graphPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) +{ + Marker *markerPtr; + Tcl_Obj *const *options; + const char *oldName; + const char *string; + int flags = BLT_CONFIG_OBJV_ONLY; + int nNames, nOpts; + int i; + int under; + + markerPtr = NULL; /* Suppress compiler warning. */ + + /* Figure out where the option value pairs begin */ + objc -= 3; + objv += 3; + for (i = 0; i < objc; i++) { + string = Tcl_GetString(objv[i]); + if (string[0] == '-') { + break; + } + if (GetMarkerFromObj(interp, graphPtr, objv[i], &markerPtr) != TCL_OK) { + return TCL_ERROR; + } + } + nNames = i; /* # of element names specified */ + nOpts = objc - i; /* # of options specified */ + options = objv + nNames; /* Start of options in objv */ + + for (i = 0; i < nNames; i++) { + GetMarkerFromObj(interp, graphPtr, objv[i], &markerPtr); + if (nOpts == 0) { + return Blt_ConfigureInfoFromObj(interp, graphPtr->tkwin, + markerPtr->classPtr->configSpecs, (char *)markerPtr, + (Tcl_Obj *)NULL, flags); + } else if (nOpts == 1) { + return Blt_ConfigureInfoFromObj(interp, graphPtr->tkwin, + markerPtr->classPtr->configSpecs, (char *)markerPtr, + options[0], flags); + } + /* Save the old marker name. */ + oldName = markerPtr->obj.name; + under = markerPtr->drawUnder; + if (Blt_ConfigureWidgetFromObj(interp, graphPtr->tkwin, + markerPtr->classPtr->configSpecs, nOpts, options, + (char *)markerPtr, flags) != TCL_OK) { + return TCL_ERROR; + } + if (oldName != markerPtr->obj.name) { + if (RenameMarker(graphPtr, markerPtr, oldName, markerPtr->obj.name) + != TCL_OK) { + markerPtr->obj.name = oldName; + return TCL_ERROR; + } + } + if ((*markerPtr->classPtr->configProc) (markerPtr) != TCL_OK) { + + return TCL_ERROR; + } + if (markerPtr->drawUnder != under) { + graphPtr->flags |= CACHE_DIRTY; + } + } + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * CreateOp -- + * + * This procedure creates and initializes a new marker. + * + * Results: + * The return value is a pointer to a structure describing the new + * element. If an error occurred, then the return value is NULL and an + * error message is left in interp->result. + * + * Side effects: + * Memory is allocated, etc. + * + *--------------------------------------------------------------------------- + */ +static int +CreateOp(Graph *graphPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) +{ + Marker *markerPtr; + Blt_HashEntry *hPtr; + int isNew; + ClassId classId; + int i; + const char *name; + char ident[200]; + const char *string; + char c; + + string = Tcl_GetString(objv[3]); + c = string[0]; + /* Create the new marker based upon the given type */ + if ((c == 't') && (strcmp(string, "text") == 0)) { + classId = CID_MARKER_TEXT; + } else if ((c == 'l') && (strcmp(string, "line") == 0)) { + classId = CID_MARKER_LINE; + } else if ((c == 'p') && (strcmp(string, "polygon") == 0)) { + classId = CID_MARKER_POLYGON; + } else if ((c == 'i') && (strcmp(string, "image") == 0)) { + classId = CID_MARKER_IMAGE; + } else if ((c == 'b') && (strcmp(string, "bitmap") == 0)) { + classId = CID_MARKER_BITMAP; + } else if ((c == 'w') && (strcmp(string, "window") == 0)) { + classId = CID_MARKER_WINDOW; + } else { + Tcl_AppendResult(interp, "unknown marker type \"", string, + "\": should be \"text\", \"line\", \"polygon\", \"bitmap\", \"image\", or \ +\"window\"", (char *)NULL); + return TCL_ERROR; + } + /* Scan for "-name" option. We need it for the component name */ + name = NULL; + for (i = 4; i < objc; i += 2) { + int length; + + string = Tcl_GetStringFromObj(objv[i], &length); + if ((length > 1) && (strncmp(string, "-name", length) == 0)) { + name = Tcl_GetString(objv[i + 1]); + break; + } + } + /* If no name was given for the marker, make up one. */ + if (name == NULL) { + sprintf_s(ident, 200, "marker%d", graphPtr->nextMarkerId++); + name = ident; + } else if (name[0] == '-') { + Tcl_AppendResult(interp, "name of marker \"", name, + "\" can't start with a '-'", (char *)NULL); + return TCL_ERROR; + } + markerPtr = CreateMarker(graphPtr, name, classId); + if (Blt_ConfigureComponentFromObj(interp, graphPtr->tkwin, name, + markerPtr->obj.className, markerPtr->classPtr->configSpecs, + objc - 4, objv + 4, (char *)markerPtr, 0) != TCL_OK) { + DestroyMarker(markerPtr); + return TCL_ERROR; + } + if ((*markerPtr->classPtr->configProc) (markerPtr) != TCL_OK) { + DestroyMarker(markerPtr); + return TCL_ERROR; + } + hPtr = Blt_CreateHashEntry(&graphPtr->markers.table, name, &isNew); + if (!isNew) { + Marker *oldPtr; + /* + * Marker by the same name already exists. Delete the old marker and + * it's list entry. But save the hash entry. + */ + oldPtr = Blt_GetHashValue(hPtr); + oldPtr->hashPtr = NULL; + DestroyMarker(oldPtr); + } + Blt_SetHashValue(hPtr, markerPtr); + markerPtr->hashPtr = hPtr; + /* Unlike elements, new markers are drawn on top of old markers. */ + markerPtr->link = Blt_Chain_Prepend(graphPtr->markers.displayList,markerPtr); + if (markerPtr->drawUnder) { + graphPtr->flags |= CACHE_DIRTY; + } + Blt_EventuallyRedrawGraph(graphPtr); + Tcl_SetStringObj(Tcl_GetObjResult(interp), name, -1); + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * DeleteOp -- + * + * Deletes the marker given by markerId. + * + * Results: + * The return value is a standard TCL result. + * + * Side Effects: + * Graph will be redrawn to reflect the new display list. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +DeleteOp(Graph *graphPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) +{ + int i; + + for (i = 3; i < objc; i++) { + Marker *markerPtr; + + if (GetMarkerFromObj(NULL, graphPtr, objv[i], &markerPtr) == TCL_OK) { + markerPtr->flags |= DELETE_PENDING; + Tcl_EventuallyFree(markerPtr, FreeMarker); + } + } + Blt_EventuallyRedrawGraph(graphPtr); + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * GetOp -- + * + * Find the legend entry from the given argument. The argument can be + * either a screen position "@x,y" or the name of an element. + * + * I don't know how useful it is to test with the name of an element. + * + * Results: + * A standard TCL result. + * + * Side Effects: + * Graph will be redrawn to reflect the new legend attributes. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +GetOp(Graph *graphPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) +{ + Marker *markerPtr; + const char *string; + + string = Tcl_GetString(objv[3]); + if ((string[0] == 'c') && (strcmp(string, "current") == 0)) { + markerPtr = (Marker *)Blt_GetCurrentItem(graphPtr->bindTable); + if (markerPtr == NULL) { + return TCL_OK; /* Report only on markers. */ + + } + if ((markerPtr->obj.classId >= CID_MARKER_BITMAP) && + (markerPtr->obj.classId <= CID_MARKER_WINDOW)) { + Tcl_SetStringObj(Tcl_GetObjResult(interp), + markerPtr->obj.name, -1); + } + } + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * RelinkOp -- + * + * Reorders the marker (given by the first name) before/after the another + * marker (given by the second name) in the marker display list. If no + * second name is given, the marker is placed at the beginning/end of the + * list. + * + * Results: + * A standard TCL result. + * + * Side Effects: + * Graph will be redrawn to reflect the new display list. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +RelinkOp(Graph *graphPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) +{ + Blt_ChainLink link, place; + Marker *markerPtr; + const char *string; + + /* Find the marker to be raised or lowered. */ + if (GetMarkerFromObj(interp, graphPtr, objv[3], &markerPtr) != TCL_OK) { + return TCL_ERROR; + } + /* Right now it's assumed that all markers are always in the display + list. */ + link = markerPtr->link; + Blt_Chain_UnlinkLink(graphPtr->markers.displayList, markerPtr->link); + + place = NULL; + if (objc == 5) { + if (GetMarkerFromObj(interp, graphPtr, objv[4], &markerPtr) != TCL_OK) { + return TCL_ERROR; + } + place = markerPtr->link; + } + + /* Link the marker at its new position. */ + string = Tcl_GetString(objv[2]); + if (string[0] == 'l') { + Blt_Chain_LinkAfter(graphPtr->markers.displayList, link, place); + } else { + Blt_Chain_LinkBefore(graphPtr->markers.displayList, link, place); + } + if (markerPtr->drawUnder) { + graphPtr->flags |= CACHE_DIRTY; + } + Blt_EventuallyRedrawGraph(graphPtr); + return TCL_OK; +} + + +/* + *--------------------------------------------------------------------------- + * + * FindOp -- + * + * Returns if marker by a given ID currently exists. + * + * Results: + * A standard TCL result. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +FindOp(Graph *graphPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) +{ + Blt_ChainLink link; + Region2d extents; + const char *string; + int enclosed; + int left, right, top, bottom; + int mode; + +#define FIND_ENCLOSED (1<<0) +#define FIND_OVERLAPPING (1<<1) + string = Tcl_GetString(objv[3]); + if (strcmp(string, "enclosed") == 0) { + mode = FIND_ENCLOSED; + } else if (strcmp(string, "overlapping") == 0) { + mode = FIND_OVERLAPPING; + } else { + Tcl_AppendResult(interp, "bad search type \"", string, + ": should be \"enclosed\", or \"overlapping\"", (char *)NULL); + return TCL_ERROR; + } + + if ((Tcl_GetIntFromObj(interp, objv[4], &left) != TCL_OK) || + (Tcl_GetIntFromObj(interp, objv[5], &top) != TCL_OK) || + (Tcl_GetIntFromObj(interp, objv[6], &right) != TCL_OK) || + (Tcl_GetIntFromObj(interp, objv[7], &bottom) != TCL_OK)) { + return TCL_ERROR; + } + if (left < right) { + extents.left = (double)left; + extents.right = (double)right; + } else { + extents.left = (double)right; + extents.right = (double)left; + } + if (top < bottom) { + extents.top = (double)top; + extents.bottom = (double)bottom; + } else { + extents.top = (double)bottom; + extents.bottom = (double)top; + } + enclosed = (mode == FIND_ENCLOSED); + for (link = Blt_Chain_FirstLink(graphPtr->markers.displayList); + link != NULL; link = Blt_Chain_NextLink(link)) { + Marker *markerPtr; + + markerPtr = Blt_Chain_GetValue(link); + if (markerPtr->flags & (HIDE|DELETE_PENDING)) { + continue; + } + if ((markerPtr->elemName != NULL) && (IsElementHidden(markerPtr))) { + continue; + } + if ((*markerPtr->classPtr->regionProc)(markerPtr, &extents, enclosed)) { + Tcl_Obj *objPtr; + + objPtr = Tcl_GetObjResult(interp); + Tcl_SetStringObj(objPtr, markerPtr->obj.name, -1); + return TCL_OK; + } + } + Tcl_SetStringObj(Tcl_GetObjResult(interp), "", -1); + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * ExistsOp -- + * + * Returns if marker by a given ID currently exists. + * + * Results: + * A standard TCL result. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +ExistsOp(Graph *graphPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) +{ + Blt_HashEntry *hPtr; + + hPtr = Blt_FindHashEntry(&graphPtr->markers.table, Tcl_GetString(objv[3])); + Tcl_SetBooleanObj(Tcl_GetObjResult(interp), (hPtr != NULL)); + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * TypeOp -- + * + * Returns a symbolic name for the type of the marker whose ID is given. + * + * Results: + * A standard TCL result. interp->result will contain the symbolic type + * of the marker. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +TypeOp(Graph *graphPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) +{ + Marker *markerPtr; + const char *type; + + if (GetMarkerFromObj(interp, graphPtr, objv[3], &markerPtr) != TCL_OK) { + return TCL_ERROR; + } + switch (markerPtr->obj.classId) { + case CID_MARKER_BITMAP: type = "bitmap"; break; + case CID_MARKER_IMAGE: type = "image"; break; + case CID_MARKER_LINE: type = "line"; break; + case CID_MARKER_POLYGON: type = "polygon"; break; + case CID_MARKER_TEXT: type = "text"; break; + case CID_MARKER_WINDOW: type = "window"; break; + default: type = "???"; break; + } + Tcl_SetStringObj(Tcl_GetObjResult(interp), type, -1); + return TCL_OK; +} + +/* Public routines */ + +/* + *--------------------------------------------------------------------------- + * + * Blt_MarkerOp -- + * + * This procedure is invoked to process the TCL command that corresponds + * to a widget managed by this module. See the user documentation for + * details on what it does. + * + * Results: + * A standard TCL result. + * + * Side effects: + * See the user documentation. + * + *--------------------------------------------------------------------------- + */ + +static Blt_OpSpec markerOps[] = +{ + {"bind", 1, BindOp, 3, 6, "marker sequence command",}, + {"cget", 2, CgetOp, 5, 5, "marker option",}, + {"configure", 2, ConfigureOp, 4, 0,"marker ?marker?... ?option value?...",}, + {"create", 2, CreateOp, 4, 0, "type ?option value?...",}, + {"delete", 1, DeleteOp, 3, 0, "?marker?...",}, + {"exists", 1, ExistsOp, 4, 4, "marker",}, + {"find", 1, FindOp, 8, 8, "enclosed|overlapping x1 y1 x2 y2",}, + {"get", 1, GetOp, 4, 4, "name",}, + {"lower", 1, RelinkOp, 4, 5, "marker ?afterMarker?",}, + {"names", 1, NamesOp, 3, 0, "?pattern?...",}, + {"raise", 1, RelinkOp, 4, 5, "marker ?beforeMarker?",}, + {"type", 1, TypeOp, 4, 4, "marker",}, +}; +static int nMarkerOps = sizeof(markerOps) / sizeof(Blt_OpSpec); + +/*ARGSUSED*/ +int +Blt_MarkerOp(Graph *graphPtr, Tcl_Interp *interp, int objc, + Tcl_Obj *const *objv) +{ + GraphMarkerProc *proc; + int result; + + proc = Blt_GetOpFromObj(interp, nMarkerOps, markerOps, BLT_OP_ARG2, + objc, objv,0); + if (proc == NULL) { + return TCL_ERROR; + } + result = (*proc) (graphPtr, interp, objc, objv); + return result; +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_MarkersToPostScript -- + * + *--------------------------------------------------------------------------- + */ +void +Blt_MarkersToPostScript(Graph *graphPtr, Blt_Ps ps, int under) +{ + Blt_ChainLink link; + + for (link = Blt_Chain_LastLink(graphPtr->markers.displayList); + link != NULL; link = Blt_Chain_PrevLink(link)) { + Marker *markerPtr; + + markerPtr = Blt_Chain_GetValue(link); + if ((markerPtr->classPtr->postscriptProc == NULL) || + (markerPtr->nWorldPts == 0)) { + continue; + } + if (markerPtr->drawUnder != under) { + continue; + } + if (markerPtr->flags & (HIDE|DELETE_PENDING)) { + continue; + } + if ((markerPtr->elemName != NULL) && (IsElementHidden(markerPtr))) { + continue; + } + Blt_Ps_VarAppend(ps, "\n% Marker \"", markerPtr->obj.name, + "\" is a ", markerPtr->obj.className, ".\n", (char *)NULL); + (*markerPtr->classPtr->postscriptProc) (markerPtr, ps); + } +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_DrawMarkers -- + * + * Calls the individual drawing routines (based on marker type) for each + * marker in the display list. + * + * A marker will not be drawn if + * + * 1) An element linked to the marker (indicated by elemName) is + * currently hidden. + * + * 2) No coordinates have been specified for the marker. + * + * 3) The marker is requesting to be drawn at a different level + * (above/below the elements) from the current mode. + * + * 4) The marker is configured as hidden (-hide option). + * + * 5) The marker isn't visible in the current viewport (i.e. clipped). + * + * Results: + * None + * + * Side Effects: + * Markers are drawn into the drawable (pixmap) which will eventually + * be displayed in the graph window. + * + *--------------------------------------------------------------------------- + */ +void +Blt_DrawMarkers(Graph *graphPtr, Drawable drawable, int under) +{ + Blt_ChainLink link; + + for (link = Blt_Chain_LastLink(graphPtr->markers.displayList); + link != NULL; link = Blt_Chain_PrevLink(link)) { + Marker *markerPtr; + + markerPtr = Blt_Chain_GetValue(link); + + if ((markerPtr->nWorldPts == 0) || + (markerPtr->drawUnder != under) || + (markerPtr->clipped) || + (markerPtr->flags & (DELETE_PENDING|HIDE))) { + continue; + } + if ((markerPtr->elemName != NULL) && (IsElementHidden(markerPtr))) { + continue; + } + (*markerPtr->classPtr->drawProc) (markerPtr, drawable); + } +} + +void +Blt_ConfigureMarkers(Graph *graphPtr) +{ + Blt_ChainLink link; + + for (link = Blt_Chain_FirstLink(graphPtr->markers.displayList); + link != NULL; link = Blt_Chain_NextLink(link)) { + Marker *markerPtr; + + markerPtr = Blt_Chain_GetValue(link); + (*markerPtr->classPtr->configProc) (markerPtr); + } +} + +void +Blt_MapMarkers(Graph *graphPtr) +{ + Blt_ChainLink link; + + for (link = Blt_Chain_FirstLink(graphPtr->markers.displayList); + link != NULL; link = Blt_Chain_NextLink(link)) { + Marker *markerPtr; + + markerPtr = Blt_Chain_GetValue(link); + if (markerPtr->nWorldPts == 0) { + continue; + } + if (markerPtr->flags & (HIDE|DELETE_PENDING)) { + continue; + } + if ((graphPtr->flags & MAP_ALL) || (markerPtr->flags & MAP_ITEM)) { + (*markerPtr->classPtr->mapProc) (markerPtr); + markerPtr->flags &= ~MAP_ITEM; + } + } +} + +void +Blt_DestroyMarkers(Graph *graphPtr) +{ + Blt_HashEntry *hPtr; + Blt_HashSearch iter; + + for (hPtr = Blt_FirstHashEntry(&graphPtr->markers.table, &iter); + hPtr != NULL; hPtr = Blt_NextHashEntry(&iter)) { + Marker *markerPtr; + + markerPtr = Blt_GetHashValue(hPtr); + /* + * Dereferencing the pointer to the hash table prevents the hash table + * entry from being automatically deleted. + */ + markerPtr->hashPtr = NULL; + DestroyMarker(markerPtr); + } + Blt_DeleteHashTable(&graphPtr->markers.table); + Blt_DeleteHashTable(&graphPtr->markers.tagTable); + Blt_Chain_Destroy(graphPtr->markers.displayList); +} + +Marker * +Blt_NearestMarker( + Graph *graphPtr, + int x, int y, /* Screen coordinates */ + int under) +{ + Blt_ChainLink link; + Point2d point; + + point.x = (double)x; + point.y = (double)y; + for (link = Blt_Chain_FirstLink(graphPtr->markers.displayList); + link != NULL; link = Blt_Chain_NextLink(link)) { + Marker *markerPtr; + + markerPtr = Blt_Chain_GetValue(link); + if ((markerPtr->nWorldPts == 0) || + (markerPtr->flags & (HIDE|DELETE_PENDING|MAP_ITEM))) { + continue; /* Don't consider markers that are + * pending to be mapped. Even if the + * marker has already been mapped, the + * coordinates could be invalid now. + * Better to pick no marker than the + * wrong marker. */ + + } + if ((markerPtr->elemName != NULL) && (IsElementHidden(markerPtr))) { + continue; + } + if ((markerPtr->drawUnder == under) && + (markerPtr->state == STATE_NORMAL)) { + if ((*markerPtr->classPtr->pointProc) (markerPtr, &point)) { + return markerPtr; + } + } + } + return NULL; +} + +ClientData +Blt_MakeMarkerTag(Graph *graphPtr, const char *tagName) +{ + Blt_HashEntry *hPtr; + int isNew; + + assert(tagName != NULL); + hPtr = Blt_CreateHashEntry(&graphPtr->markers.tagTable, tagName, &isNew); + return Blt_GetHashKey(&graphPtr->markers.tagTable, hPtr); +} + + +#ifdef notdef +/* + *--------------------------------------------------------------------------- + * + * ConfigureArrows -- + * + * If arrowheads have been requested for a line, this procedure makes + * arrangements for the arrowheads. + * + * Results: + * Always returns TCL_OK. + * + * Side effects: + * Information in linePtr is set up for one or two arrowheads. the + * firstArrowPtr and lastArrowPtr polygons are allocated and initialized, + * if need be, and the end points of the line are adjusted so that a + * thick line doesn't stick out past the arrowheads. + * + *--------------------------------------------------------------------------- + */ + + /* ARGSUSED */ +static int +ConfigureArrows(canvas, linePtr) + Tk_Canvas canvas; /* Canvas in which arrows will be + * displayed (interp and tkwin fields + * are needed). */ + LineItem *linePtr; /* Item to configure for arrows. */ +{ + double *poly, *coordPtr; + double dx, dy, length, sinTheta, cosTheta, temp; + double fracHeight; /* Line width as fraction of arrowhead + * width. */ + double backup; /* Distance to backup end points so + * the line ends in the middle of the + * arrowhead. */ + double vertX, vertY; /* Position of arrowhead vertex. */ + double shapeA, shapeB, shapeC; /* Adjusted coordinates (see + * explanation below). */ + double width; + Tk_State state = linePtr->header.state; + + if (linePtr->numPoints <2) { + return TCL_OK; + } + + if(state == TK_STATE_NULL) { + state = ((TkCanvas *)canvas)->canvas_state; + } + + width = linePtr->outline.width; + if (((TkCanvas *)canvas)->currentItemPtr == (Tk_Item *)linePtr) { + if (linePtr->outline.activeWidth>width) { + width = linePtr->outline.activeWidth; + } + } else if (state==TK_STATE_DISABLED) { + if (linePtr->outline.disabledWidth>0) { + width = linePtr->outline.disabledWidth; + } + } + + /* + * The code below makes a tiny increase in the shape parameters for the + * line. This is a bit of a hack, but it seems to result in displays that + * more closely approximate the specified parameters. Without the + * adjustment, the arrows come out smaller than expected. + */ + + shapeA = linePtr->arrowShapeA + 0.001; + shapeB = linePtr->arrowShapeB + 0.001; + shapeC = linePtr->arrowShapeC + width/2.0 + 0.001; + + /* + * If there's an arrowhead on the first point of the line, compute its + * polygon and adjust the first point of the line so that the line doesn't + * stick out past the leading edge of the arrowhead. + */ + + fracHeight = (width/2.0)/shapeC; + backup = fracHeight*shapeB + shapeA*(1.0 - fracHeight)/2.0; + if (linePtr->arrow != ARROWS_LAST) { + poly = linePtr->firstArrowPtr; + if (poly == NULL) { + poly = (double *) ckalloc((unsigned) + (2*PTS_IN_ARROW*sizeof(double))); + poly[0] = poly[10] = linePtr->coordPtr[0]; + poly[1] = poly[11] = linePtr->coordPtr[1]; + linePtr->firstArrowPtr = poly; + } + dx = poly[0] - linePtr->coordPtr[2]; + dy = poly[1] - linePtr->coordPtr[3]; + length = hypot(dx, dy); + if (length == 0) { + sinTheta = cosTheta = 0.0; + } else { + sinTheta = dy/length; + cosTheta = dx/length; + } + vertX = poly[0] - shapeA*cosTheta; + vertY = poly[1] - shapeA*sinTheta; + temp = shapeC*sinTheta; + poly[2] = poly[0] - shapeB*cosTheta + temp; + poly[8] = poly[2] - 2*temp; + temp = shapeC*cosTheta; + poly[3] = poly[1] - shapeB*sinTheta - temp; + poly[9] = poly[3] + 2*temp; + poly[4] = poly[2]*fracHeight + vertX*(1.0-fracHeight); + poly[5] = poly[3]*fracHeight + vertY*(1.0-fracHeight); + poly[6] = poly[8]*fracHeight + vertX*(1.0-fracHeight); + poly[7] = poly[9]*fracHeight + vertY*(1.0-fracHeight); + + /* + * Polygon done. Now move the first point towards the second so that + * the corners at the end of the line are inside the arrowhead. + */ + + linePtr->coordPtr[0] = poly[0] - backup*cosTheta; + linePtr->coordPtr[1] = poly[1] - backup*sinTheta; + } + + /* + * Similar arrowhead calculation for the last point of the line. + */ + + if (linePtr->arrow != ARROWS_FIRST) { + coordPtr = linePtr->coordPtr + 2*(linePtr->numPoints-2); + poly = linePtr->lastArrowPtr; + if (poly == NULL) { + poly = (double *) ckalloc((unsigned) + (2*PTS_IN_ARROW*sizeof(double))); + poly[0] = poly[10] = coordPtr[2]; + poly[1] = poly[11] = coordPtr[3]; + linePtr->lastArrowPtr = poly; + } + dx = poly[0] - coordPtr[0]; + dy = poly[1] - coordPtr[1]; + length = hypot(dx, dy); + if (length == 0) { + sinTheta = cosTheta = 0.0; + } else { + sinTheta = dy/length; + cosTheta = dx/length; + } + vertX = poly[0] - shapeA*cosTheta; + vertY = poly[1] - shapeA*sinTheta; + temp = shapeC*sinTheta; + poly[2] = poly[0] - shapeB*cosTheta + temp; + poly[8] = poly[2] - 2*temp; + temp = shapeC*cosTheta; + poly[3] = poly[1] - shapeB*sinTheta - temp; + poly[9] = poly[3] + 2*temp; + poly[4] = poly[2]*fracHeight + vertX*(1.0-fracHeight); + poly[5] = poly[3]*fracHeight + vertY*(1.0-fracHeight); + poly[6] = poly[8]*fracHeight + vertX*(1.0-fracHeight); + poly[7] = poly[9]*fracHeight + vertY*(1.0-fracHeight); + coordPtr[2] = poly[0] - backup*cosTheta; + coordPtr[3] = poly[1] - backup*sinTheta; + } + + return TCL_OK; +} +#endif diff --git a/src/bltGrMisc.C b/src/bltGrMisc.C new file mode 100644 index 0000000..101273e --- /dev/null +++ b/src/bltGrMisc.C @@ -0,0 +1,2024 @@ + + +/* + * bltGrMisc.c -- + * + * This module implements miscellaneous routines for the BLT graph widget. + * + * Copyright 1993-2004 George A Howlett. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include "bltGraph.h" +#include <X11/Xutil.h> +#include <bltAlloc.h> +#include <bltOp.h> +#include <stdarg.h> + +static Blt_OptionParseProc ObjToPoint; +static Blt_OptionPrintProc PointToObj; +Blt_CustomOption bltPointOption = +{ + ObjToPoint, PointToObj, NULL, (ClientData)0 +}; + +static Blt_OptionParseProc ObjToLimitsProc; +static Blt_OptionPrintProc LimitsToObjProc; +Blt_CustomOption bltLimitsOption = +{ + ObjToLimitsProc, LimitsToObjProc, NULL, (ClientData)0 +}; + + +/* + *--------------------------------------------------------------------------- + * Custom option parse and print procedures + *--------------------------------------------------------------------------- + */ + +/* + *--------------------------------------------------------------------------- + * + * Blt_GetXY -- + * + * Converts a string in the form "@x,y" into an XPoint structure of the x + * and y coordinates. + * + * Results: + * A standard TCL result. If the string represents a valid position + * *pointPtr* will contain the converted x and y coordinates and TCL_OK + * is returned. Otherwise, TCL_ERROR is returned and interp->result will + * contain an error message. + * + *--------------------------------------------------------------------------- + */ +int +Blt_GetXY(Tcl_Interp *interp, Tk_Window tkwin, const char *string, + int *xPtr, int *yPtr) +{ + char *comma; + int result; + int x, y; + + if ((string == NULL) || (*string == '\0')) { + *xPtr = *yPtr = -SHRT_MAX; + return TCL_OK; + } + if (*string != '@') { + goto badFormat; + } + comma = strchr(string + 1, ','); + if (comma == NULL) { + goto badFormat; + } + *comma = '\0'; + result = ((Tk_GetPixels(interp, tkwin, string + 1, &x) == TCL_OK) && + (Tk_GetPixels(interp, tkwin, comma + 1, &y) == TCL_OK)); + *comma = ','; + if (!result) { + Tcl_AppendResult(interp, ": can't parse position \"", string, "\"", + (char *)NULL); + return TCL_ERROR; + } + *xPtr = x, *yPtr = y; + return TCL_OK; + + badFormat: + Tcl_AppendResult(interp, "bad position \"", string, + "\": should be \"@x,y\"", (char *)NULL); + return TCL_ERROR; +} + +/* + *--------------------------------------------------------------------------- + * + * ObjToPoint -- + * + * Convert the string representation of a legend XY position into window + * coordinates. The form of the string must be "@x,y" or none. + * + * Results: + * A standard TCL result. The symbol type is written into the + * widget record. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +ObjToPoint( + ClientData clientData, /* Not used. */ + Tcl_Interp *interp, /* Interpreter to send results back to */ + Tk_Window tkwin, /* Not used. */ + Tcl_Obj *objPtr, /* New legend position string */ + char *widgRec, /* Widget record */ + int offset, /* Offset to field in structure */ + int flags) /* Not used. */ +{ + XPoint *pointPtr = (XPoint *)(widgRec + offset); + int x, y; + + if (Blt_GetXY(interp, tkwin, Tcl_GetString(objPtr), &x, &y) != TCL_OK) { + return TCL_ERROR; + } + pointPtr->x = x, pointPtr->y = y; + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * PointToObj -- + * + * Convert the window coordinates into a string. + * + * Results: + * The string representing the coordinate position is returned. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static Tcl_Obj * +PointToObj( + ClientData clientData, /* Not used. */ + Tcl_Interp *interp, /* Not used. */ + Tk_Window tkwin, /* Not used. */ + char *widgRec, /* Widget record */ + int offset, /* Offset to field in structure */ + int flags) /* Not used. */ +{ + XPoint *pointPtr = (XPoint *)(widgRec + offset); + Tcl_Obj *objPtr; + + if ((pointPtr->x != -SHRT_MAX) && (pointPtr->y != -SHRT_MAX)) { + char string[200]; + + sprintf_s(string, 200, "@%d,%d", pointPtr->x, pointPtr->y); + objPtr = Tcl_NewStringObj(string, -1); + } else { + objPtr = Tcl_NewStringObj("", -1); + } + return objPtr; +} + +/* + *--------------------------------------------------------------------------- + * + * ObjToLimitsProc -- + * + * Converts the list of elements into zero or more pixel values which + * determine the range of pixel values possible. An element can be in any + * form accepted by Tk_GetPixels. The list has a different meaning based + * upon the number of elements. + * + * # of elements: + * + * 0 - the limits are reset to the defaults. + * 1 - the minimum and maximum values are set to this + * value, freezing the range at a single value. + * 2 - first element is the minimum, the second is the + * maximum. + * 3 - first element is the minimum, the second is the + * maximum, and the third is the nominal value. + * + * Any element may be the empty string which indicates the default. + * + * Results: + * The return value is a standard TCL result. The min and max fields + * of the range are set. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +ObjToLimitsProc( + ClientData clientData, /* Not used. */ + Tcl_Interp *interp, /* Interpreter to send results back + * to */ + Tk_Window tkwin, /* Widget of paneset */ + Tcl_Obj *objPtr, /* New width list */ + char *widgRec, /* Widget record */ + int offset, /* Offset to field in structure */ + int flags) +{ + Blt_Limits *limitsPtr = (Blt_Limits *)(widgRec + offset); + + if (Blt_GetLimitsFromObj(interp, tkwin, objPtr, limitsPtr) != TCL_OK) { + return TCL_ERROR; + } + return TCL_OK; +} + + +/* + *--------------------------------------------------------------------------- + * + * LimitsToObjProc -- + * + * Convert the limits of the pixel values allowed into a list. + * + * Results: + * The string representation of the limits is returned. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static Tcl_Obj * +LimitsToObjProc( + ClientData clientData, /* Not used. */ + Tcl_Interp *interp, /* Not used. */ + Tk_Window tkwin, /* Not used. */ + char *widgRec, /* Row/column structure record */ + int offset, /* Offset to field in structure */ + int flags) +{ + Blt_Limits *limitsPtr = (Blt_Limits *)(widgRec + offset); + Tcl_Obj *listObjPtr; + + listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL); + if (limitsPtr->flags & LIMITS_MIN_SET) { + Tcl_ListObjAppendElement(interp, listObjPtr, + Tcl_NewIntObj(limitsPtr->min)); + } else { + Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewStringObj("", -1)); + } + if (limitsPtr->flags & LIMITS_MAX_SET) { + Tcl_ListObjAppendElement(interp, listObjPtr, + Tcl_NewIntObj(limitsPtr->max)); + } else { + Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewStringObj("", -1)); + } + if (limitsPtr->flags & LIMITS_NOM_SET) { + Tcl_ListObjAppendElement(interp, listObjPtr, + Tcl_NewIntObj(limitsPtr->nom)); + } else { + Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewStringObj("", -1)); + } + return listObjPtr; +} + +int +Blt_PointInSegments( + Point2d *samplePtr, + Segment2d *segments, + int nSegments, + double halo) +{ + Segment2d *sp, *send; + double minDist; + + minDist = DBL_MAX; + for (sp = segments, send = sp + nSegments; sp < send; sp++) { + double dist; + double left, right, top, bottom; + Point2d p, t; + + t = Blt_GetProjection((int)samplePtr->x, (int)samplePtr->y, + &sp->p, &sp->q); + if (sp->p.x > sp->q.x) { + right = sp->p.x, left = sp->q.x; + } else { + right = sp->q.x, left = sp->p.x; + } + if (sp->p.y > sp->q.y) { + bottom = sp->p.y, top = sp->q.y; + } else { + bottom = sp->q.y, top = sp->p.y; + } + p.x = BOUND(t.x, left, right); + p.y = BOUND(t.y, top, bottom); + dist = hypot(p.x - samplePtr->x, p.y - samplePtr->y); + if (dist < minDist) { + minDist = dist; + } + } + return (minDist < halo); +} + +int +Blt_PointInPolygon( + Point2d *s, /* Sample point. */ + Point2d *points, /* Points representing the polygon. */ + int nPoints) /* # of points in above array. */ +{ + Point2d *p, *q, *qend; + int count; + + count = 0; + for (p = points, q = p + 1, qend = p + nPoints; q < qend; p++, q++) { + if (((p->y <= s->y) && (s->y < q->y)) || + ((q->y <= s->y) && (s->y < p->y))) { + double b; + + b = (q->x - p->x) * (s->y - p->y) / (q->y - p->y) + p->x; + if (s->x < b) { + count++; /* Count the number of intersections. */ + } + } + } + return (count & 0x01); +} + +int +Blt_RegionInPolygon( + Region2d *regionPtr, + Point2d *points, + int nPoints, + int enclosed) +{ + Point2d *pp, *pend; + + if (enclosed) { + /* + * All points of the polygon must be inside the rectangle. + */ + for (pp = points, pend = pp + nPoints; pp < pend; pp++) { + if ((pp->x < regionPtr->left) || (pp->x > regionPtr->right) || + (pp->y < regionPtr->top) || (pp->y > regionPtr->bottom)) { + return FALSE; /* One point is exterior. */ + } + } + return TRUE; + } else { + Point2d r; + /* + * If any segment of the polygon clips the bounding region, the + * polygon overlaps the rectangle. + */ + points[nPoints] = points[0]; + for (pp = points, pend = pp + nPoints; pp < pend; pp++) { + Point2d p, q; + + p = *pp; + q = *(pp + 1); + if (Blt_LineRectClip(regionPtr, &p, &q)) { + return TRUE; + } + } + /* + * Otherwise the polygon and rectangle are either disjoint or + * enclosed. Check if one corner of the rectangle is inside the + * polygon. + */ + r.x = regionPtr->left; + r.y = regionPtr->top; + return Blt_PointInPolygon(&r, points, nPoints); + } +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_GraphExtents -- + * + * Generates a bounding box representing the plotting area of the + * graph. This data structure is used to clip the points and line + * segments of the line element. + * + * The clip region is the plotting area plus such arbitrary extra space. + * The reason we clip with a bounding box larger than the plot area is so + * that symbols will be drawn even if their center point isn't in the + * plotting area. + * + * Results: + * None. + * + * Side Effects: + * The bounding box is filled with the dimensions of the plotting area. + * + *--------------------------------------------------------------------------- + */ +void +Blt_GraphExtents(Graph *graphPtr, Region2d *regionPtr) +{ + regionPtr->left = (double)(graphPtr->hOffset - graphPtr->xPad.side1); + regionPtr->top = (double)(graphPtr->vOffset - graphPtr->yPad.side1); + regionPtr->right = (double)(graphPtr->hOffset + graphPtr->hRange + + graphPtr->xPad.side2); + regionPtr->bottom = (double)(graphPtr->vOffset + graphPtr->vRange + + graphPtr->yPad.side2); +} + +static int +ClipTest (double ds, double dr, double *t1, double *t2) +{ + double t; + + if (ds < 0.0) { + t = dr / ds; + if (t > *t2) { + return FALSE; + } + if (t > *t1) { + *t1 = t; + } + } else if (ds > 0.0) { + t = dr / ds; + if (t < *t1) { + return FALSE; + } + if (t < *t2) { + *t2 = t; + } + } else { + /* d = 0, so line is parallel to this clipping edge */ + if (dr < 0.0) { /* Line is outside clipping edge */ + return FALSE; + } + } + return TRUE; +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_LineRectClip -- + * + * Clips the given line segment to a rectangular region. The coordinates + * of the clipped line segment are returned. The original coordinates + * are overwritten. + * + * Reference: + * Liang, Y-D., and B. Barsky, A new concept and method for + * Line Clipping, ACM, TOG,3(1), 1984, pp.1-22. + * + * Results: + * Returns if line segment is visible within the region. The coordinates + * of the original line segment are overwritten by the clipped + * coordinates. + * + *--------------------------------------------------------------------------- + */ +int +Blt_LineRectClip( + Region2d *regionPtr, /* Rectangular region to clip. */ + Point2d *p, Point2d *q) /* (in/out) Coordinates of original and + * clipped line segment. */ +{ + double t1, t2; + double dx, dy; + + t1 = 0.0, t2 = 1.0; + dx = q->x - p->x; + if ((ClipTest (-dx, p->x - regionPtr->left, &t1, &t2)) && + (ClipTest (dx, regionPtr->right - p->x, &t1, &t2))) { + dy = q->y - p->y; + if ((ClipTest (-dy, p->y - regionPtr->top, &t1, &t2)) && + (ClipTest (dy, regionPtr->bottom - p->y, &t1, &t2))) { + if (t2 < 1.0) { + q->x = p->x + t2 * dx; + q->y = p->y + t2 * dy; + } + if (t1 > 0.0) { + p->x += t1 * dx; + p->y += t1 * dy; + } + return TRUE; + } + } + return FALSE; +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_PolyRectClip -- + * + * Clips the given polygon to a rectangular region. The resulting + * polygon is returned. Note that the resulting polyon may be complex, + * connected by zero width/height segments. The drawing routine (such as + * XFillPolygon) will not draw a connecting segment. + * + * Reference: + * Liang Y. D. and Brian A. Barsky, "Analysis and Algorithm for + * Polygon Clipping", Communications of ACM, Vol. 26, + * p.868-877, 1983 + * + * Results: + * Returns the number of points in the clipped polygon. The points of the + * clipped polygon are stored in *outputPts*. + * + *--------------------------------------------------------------------------- + */ +#define EPSILON FLT_EPSILON +#define AddVertex(vx, vy) r->x=(vx), r->y=(vy), r++, count++ +#define LastVertex(vx, vy) r->x=(vx), r->y=(vy), count++ + +int +Blt_PolyRectClip( + Region2d *regionPtr, /* Rectangular region clipping the polygon. */ + Point2d *points, /* Points of polygon to be clipped. */ + int nPoints, /* # of points in polygon. */ + Point2d *clipPts) /* (out) Points of clipped polygon. */ +{ + Point2d *p; /* First vertex of input polygon edge. */ + Point2d *pend; + Point2d *q; /* Last vertex of input polygon edge. */ + Point2d *r; + int count; + + r = clipPts; + count = 0; /* Counts # of vertices in output polygon. */ + + points[nPoints] = points[0]; + for (p = points, q = p + 1, pend = p + nPoints; p < pend; p++, q++) { + double dx, dy; + double tin1, tin2, tinx, tiny; + double xin, yin, xout, yout; + + dx = q->x - p->x; /* X-direction */ + dy = q->y - p->y; /* Y-direction */ + + if (FABS(dx) < EPSILON) { + dx = (p->x > regionPtr->left) ? -EPSILON : EPSILON ; + } + if (FABS(dy) < EPSILON) { + dy = (p->y > regionPtr->top) ? -EPSILON : EPSILON ; + } + + if (dx > 0.0) { /* Left */ + xin = regionPtr->left; + xout = regionPtr->right + 1.0; + } else { /* Right */ + xin = regionPtr->right + 1.0; + xout = regionPtr->left; + } + if (dy > 0.0) { /* Top */ + yin = regionPtr->top; + yout = regionPtr->bottom + 1.0; + } else { /* Bottom */ + yin = regionPtr->bottom + 1.0; + yout = regionPtr->top; + } + + tinx = (xin - p->x) / dx; + tiny = (yin - p->y) / dy; + + if (tinx < tiny) { /* Hits x first */ + tin1 = tinx; + tin2 = tiny; + } else { /* Hits y first */ + tin1 = tiny; + tin2 = tinx; + } + + if (tin1 <= 1.0) { + if (tin1 > 0.0) { + AddVertex(xin, yin); + } + if (tin2 <= 1.0) { + double toutx, touty, tout1; + + toutx = (xout - p->x) / dx; + touty = (yout - p->y) / dy; + tout1 = MIN(toutx, touty); + + if ((tin2 > 0.0) || (tout1 > 0.0)) { + if (tin2 <= tout1) { + if (tin2 > 0.0) { + if (tinx > tiny) { + AddVertex(xin, p->y + tinx * dy); + } else { + AddVertex(p->x + tiny * dx, yin); + } + } + if (tout1 < 1.0) { + if (toutx < touty) { + AddVertex(xout, p->y + toutx * dy); + } else { + AddVertex(p->x + touty * dx, yout); + } + } else { + AddVertex(q->x, q->y); + } + } else { + if (tinx > tiny) { + AddVertex(xin, yout); + } else { + AddVertex(xout, yin); + } + + } + } + } + } + } + if (count > 0) { + LastVertex(clipPts[0].x, clipPts[0].y); + } + return count; +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_GetProjection -- + * + * Computes the projection of a point on a line. The line (given by two + * points), is assumed the be infinite. + * + * Compute the slope (angle) of the line and rotate it 90 degrees. Using + * the slope-intercept method (we know the second line from the sample + * test point and the computed slope), then find the intersection of both + * lines. This will be the projection of the sample point on the first + * line. + * + * Results: + * Returns the coordinates of the projection on the line. + * + *--------------------------------------------------------------------------- + */ +Point2d +Blt_GetProjection( + int x, int y, /* Screen coordinates of the sample point. */ + Point2d *p, Point2d *q) /* Line segment to project point onto */ +{ + double dx, dy; + Point2d t; + + dx = p->x - q->x; + dy = p->y - q->y; + + /* Test for horizontal and vertical lines */ + if (FABS(dx) < DBL_EPSILON) { + t.x = p->x, t.y = (double)y; + } else if (FABS(dy) < DBL_EPSILON) { + t.x = (double)x, t.y = p->y; + } else { + double m1, m2; /* Slope of both lines */ + double b1, b2; /* y-intercepts */ + double midX, midY; /* Midpoint of line segment. */ + double ax, ay, bx, by; + + /* Compute the slope and intercept of PQ. */ + m1 = (dy / dx); + b1 = p->y - (p->x * m1); + + /* + * Compute the slope and intercept of a second line segment: one that + * intersects through sample X-Y coordinate with a slope perpendicular + * to original line. + */ + + /* Find midpoint of PQ. */ + midX = (p->x + q->x) * 0.5; + midY = (p->y + q->y) * 0.5; + + /* Rotate the line 90 degrees */ + ax = midX - (0.5 * dy); + ay = midY - (0.5 * -dx); + bx = midX + (0.5 * dy); + by = midY + (0.5 * -dx); + + m2 = (ay - by) / (ax - bx); + b2 = y - (x * m2); + + /* + * Given the equations of two lines which contain the same point, + * + * y = m1 * x + b1 + * y = m2 * x + b2 + * + * solve for the intersection. + * + * x = (b2 - b1) / (m1 - m2) + * y = m1 * x + b1 + * + */ + + t.x = (b2 - b1) / (m1 - m2); + t.y = m1 * t.x + b1; + } + return t; +} + +typedef struct { + double hue, sat, val; +} HSV; + +#define SetColor(c,r,g,b) ((c)->red = (int)((r) * 65535.0), \ + (c)->green = (int)((g) * 65535.0), \ + (c)->blue = (int)((b) * 65535.0)) + +#ifdef notdef +void +Blt_XColorToHSV(XColor *colorPtr, HSV *hsvPtr) +{ + unsigned short max, min; + double range; + unsigned short *colorValues; + + /* Find the minimum and maximum RGB intensities */ + colorValues = (unsigned short *)&colorPtr->red; + max = MAX3(colorValues[0], colorValues[1], colorValues[2]); + min = MIN3(colorValues[0], colorValues[1], colorValues[2]); + + hsvPtr->val = (double)max / 65535.0; + hsvPtr->hue = hsvPtr->sat = 0.0; + + range = (double)(max - min); + if (max != min) { + hsvPtr->sat = range / (double)max; + } + if (hsvPtr->sat > 0.0) { + double red, green, blue; + + /* Normalize the RGB values */ + red = (double)(max - colorPtr->red) / range; + green = (double)(max - colorPtr->green) / range; + blue = (double)(max - colorPtr->blue) / range; + + if (colorPtr->red == max) { + hsvPtr->hue = (blue - green); + } else if (colorPtr->green == max) { + hsvPtr->hue = 2 + (red - blue); + } else if (colorPtr->blue == max) { + hsvPtr->hue = 4 + (green - red); + } + hsvPtr->hue *= 60.0; + } else { + hsvPtr->sat = 0.5; + } + if (hsvPtr->hue < 0.0) { + hsvPtr->hue += 360.0; + } +} + +void +Blt_HSVToXColor(HSV *hsvPtr, XColor *colorPtr) +{ + double hue, p, q, t; + double frac; + int quadrant; + + if (hsvPtr->val < 0.0) { + hsvPtr->val = 0.0; + } else if (hsvPtr->val > 1.0) { + hsvPtr->val = 1.0; + } + if (hsvPtr->sat == 0.0) { + SetColor(colorPtr, hsvPtr->val, hsvPtr->val, hsvPtr->val); + return; + } + hue = FMOD(hsvPtr->hue, 360.0) / 60.0; + quadrant = (int)floor(hue); + frac = hsvPtr->hue - quadrant; + p = hsvPtr->val * (1 - (hsvPtr->sat)); + q = hsvPtr->val * (1 - (hsvPtr->sat * frac)); + t = hsvPtr->val * (1 - (hsvPtr->sat * (1 - frac))); + + switch (quadrant) { + case 0: + SetColor(colorPtr, hsvPtr->val, t, p); + break; + case 1: + SetColor(colorPtr, q, hsvPtr->val, p); + break; + case 2: + SetColor(colorPtr, p, hsvPtr->val, t); + break; + case 3: + SetColor(colorPtr, p, q, hsvPtr->val); + break; + case 4: + SetColor(colorPtr, t, p, hsvPtr->val); + break; + case 5: + SetColor(colorPtr, hsvPtr->val, p, q); + break; + } +} +#endif + +/* + *--------------------------------------------------------------------------- + * + * Blt_AdjustViewport -- + * + * Adjusts the offsets of the viewport according to the scroll mode. + * This is to accommodate both "listbox" and "canvas" style scrolling. + * + * "canvas" The viewport scrolls within the range of world + * coordinates. This way the viewport always displays + * a full page of the world. If the world is smaller + * than the viewport, then (bizarrely) the world and + * viewport are inverted so that the world moves up + * and down within the viewport. + * + * "listbox" The viewport can scroll beyond the range of world + * coordinates. Every entry can be displayed at the + * top of the viewport. This also means that the + * scrollbar thumb weirdly shrinks as the last entry + * is scrolled upward. + * + * Results: + * The corrected offset is returned. + * + *--------------------------------------------------------------------------- + */ +int +Blt_AdjustViewport(int offset, int worldSize, int windowSize, int scrollUnits, + int scrollMode) +{ + switch (scrollMode) { + case BLT_SCROLL_MODE_CANVAS: + + /* + * Canvas-style scrolling allows the world to be scrolled within the + * window. + */ + if (worldSize < windowSize) { + if ((worldSize - offset) > windowSize) { + offset = worldSize - windowSize; + } + if (offset > 0) { + offset = 0; + } + } else { + if ((offset + windowSize) > worldSize) { + offset = worldSize - windowSize; + } + if (offset < 0) { + offset = 0; + } + } + break; + + case BLT_SCROLL_MODE_LISTBOX: + if (offset < 0) { + offset = 0; + } + if (offset >= worldSize) { + offset = worldSize - scrollUnits; + } + break; + + case BLT_SCROLL_MODE_HIERBOX: + + /* + * Hierbox-style scrolling allows the world to be scrolled within the + * window. + */ + if ((offset + windowSize) > worldSize) { + offset = worldSize - windowSize; + } + if (offset < 0) { + offset = 0; + } + break; + } + return offset; +} + +int +Blt_GetScrollInfoFromObj(Tcl_Interp *interp, int objc, Tcl_Obj *const *objv, + int *offsetPtr, int worldSize, int windowSize, + int scrollUnits, int scrollMode) +{ + char c; + const char *string; + int length; + int offset; + + offset = *offsetPtr; + string = Tcl_GetStringFromObj(objv[0], &length); + c = string[0]; + if ((c == 's') && (strncmp(string, "scroll", length) == 0)) { + double fract; + int count; + + if (objc != 3) { + return TCL_ERROR; + } + /* Scroll number unit/page */ + if (Tcl_GetIntFromObj(interp, objv[1], &count) != TCL_OK) { + return TCL_ERROR; + } + string = Tcl_GetStringFromObj(objv[2], &length); + c = string[0]; + if ((c == 'u') && (strncmp(string, "units", length) == 0)) { + fract = (double)count *scrollUnits; + } else if ((c == 'p') && (strncmp(string, "pages", length) == 0)) { + /* A page is 90% of the view-able window. */ + fract = (double)count * windowSize * 0.9; + } else { + Tcl_AppendResult(interp, "unknown \"scroll\" units \"", + Tcl_GetString(objv[2]), "\"", (char *)NULL); + return TCL_ERROR; + } + offset += (int)fract; + } else if ((c == 'm') && (strncmp(string, "moveto", length) == 0)) { + double fract; + + if (objc != 2) { + return TCL_ERROR; + } + /* moveto fraction */ + if (Tcl_GetDoubleFromObj(interp, objv[1], &fract) != TCL_OK) { + return TCL_ERROR; + } + offset = (int)(worldSize * fract); + } else { + double fract; + int count; + + /* Treat like "scroll units" */ + if (Tcl_GetIntFromObj(interp, objv[0], &count) != TCL_OK) { + return TCL_ERROR; + } + fract = (double)count *scrollUnits; + offset += (int)fract; + } + *offsetPtr = Blt_AdjustViewport(offset, worldSize, windowSize, scrollUnits, + scrollMode); + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_UpdateScrollbar -- + * + * Invoke a TCL command to the scrollbar, defining the new position and + * length of the scroll. See the Tk documentation for further information + * on the scrollbar. It is assumed the scrollbar command prefix is + * valid. + * + * Results: + * None. + * + * Side Effects: + * Scrollbar is commanded to change position and/or size. + * + *--------------------------------------------------------------------------- + */ +void +Blt_UpdateScrollbar( + Tcl_Interp *interp, + Tcl_Obj *scrollCmdObjPtr, /* Scrollbar command prefix. May be + * several words */ + int first, int last, int width) +{ + Tcl_Obj *cmdObjPtr; + double firstFract, lastFract; + + firstFract = 0.0, lastFract = 1.0; + if (width > 0) { + firstFract = (double)first / (double)width; + lastFract = (double)last / (double)width; + } + cmdObjPtr = Tcl_DuplicateObj(scrollCmdObjPtr); + Tcl_ListObjAppendElement(interp, cmdObjPtr, Tcl_NewDoubleObj(firstFract)); + Tcl_ListObjAppendElement(interp, cmdObjPtr, Tcl_NewDoubleObj(lastFract)); + Tcl_IncrRefCount(cmdObjPtr); + if (Tcl_EvalObjEx(interp, cmdObjPtr, TCL_EVAL_GLOBAL) != TCL_OK) { + Tcl_BackgroundError(interp); + } + Tcl_DecrRefCount(cmdObjPtr); + +} + +/* -------------------------------------------------------------------------- */ +/* + *--------------------------------------------------------------------------- + * + * Blt_GetPrivateGCFromDrawable -- + * + * Like Tk_GetGC, but doesn't share the GC with any other widget. This + * is needed because the certain GC parameters (like dashes) can not be + * set via XCreateGC, therefore there is no way for Tk's hashing + * mechanism to recognize that two such GCs differ. + * + * Results: + * A new GC is returned. + * + *--------------------------------------------------------------------------- + */ +GC +Blt_GetPrivateGCFromDrawable( + Display *display, + Drawable drawable, + unsigned long gcMask, + XGCValues *valuePtr) +{ + GC newGC; + +#ifdef WIN32 + newGC = Blt_EmulateXCreateGC(display, drawable, gcMask, valuePtr); +#else + newGC = XCreateGC(display, drawable, gcMask, valuePtr); +#endif + return newGC; +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_GetPrivateGC -- + * + * Like Tk_GetGC, but doesn't share the GC with any other widget. This + * is needed because the certain GC parameters (like dashes) can not be + * set via XCreateGC, therefore there is no way for Tk's hashing + * mechanism to recognize that two such GCs differ. + * + * Results: + * A new GC is returned. + * + *--------------------------------------------------------------------------- + */ +GC +Blt_GetPrivateGC( + Tk_Window tkwin, + unsigned long gcMask, + XGCValues *valuePtr) +{ + GC gc; + Pixmap pixmap; + Drawable drawable; + Display *display; + + pixmap = None; + drawable = Tk_WindowId(tkwin); + display = Tk_Display(tkwin); + if (drawable == None) { + Drawable root; + int depth; + + root = Tk_RootWindow(tkwin); + depth = Tk_Depth(tkwin); + + if (depth == DefaultDepth(display, Tk_ScreenNumber(tkwin))) { + drawable = root; + } else { + pixmap = Tk_GetPixmap(display, root, 1, 1, depth); + drawable = pixmap; + Blt_SetDrawableAttribs(display, drawable, 1, 1, depth, + Tk_Colormap(tkwin), Tk_Visual(tkwin)); + } + } + gc = Blt_GetPrivateGCFromDrawable(display, drawable, gcMask, valuePtr); + if (pixmap != None) { + Tk_FreePixmap(display, pixmap); + } + return gc; +} + +void +Blt_FreePrivateGC(Display *display, GC gc) +{ + Tk_FreeXId(display, (XID) XGContextFromGC(gc)); + XFreeGC(display, gc); +} + +#ifndef WIN32 +void +Blt_SetDashes(Display *display, GC gc, Blt_Dashes *dashesPtr) +{ + XSetDashes(display, gc, dashesPtr->offset, (const char *)dashesPtr->values, + (int)strlen((char *)dashesPtr->values)); +} +#endif + +void +Blt_ScreenDPI(Tk_Window tkwin, unsigned int *xPtr, unsigned int *yPtr) +{ + Screen *screen; + +#define MM_INCH 25.4 + screen = Tk_Screen(tkwin); + *xPtr = (unsigned int)((WidthOfScreen(screen) * MM_INCH) / + WidthMMOfScreen(screen)); + *yPtr = (unsigned int)((HeightOfScreen(screen) * MM_INCH) / + HeightMMOfScreen(screen)); +} + +void +Blt_Draw2DSegments( + Display *display, + Drawable drawable, + GC gc, + Segment2d *segments, + int nSegments) +{ + XSegment *dp, *xsegments; + Segment2d *sp, *send; + + xsegments = Blt_Malloc(nSegments * sizeof(XSegment)); + if (xsegments == NULL) { + return; + } + dp = xsegments; + for (sp = segments, send = sp + nSegments; sp < send; sp++) { + dp->x1 = (short int)sp->p.x; + dp->y1 = (short int)sp->p.y; + dp->x2 = (short int)sp->q.x; + dp->y2 = (short int)sp->q.y; + dp++; + } + XDrawSegments(display, drawable, gc, xsegments, nSegments); + Blt_Free(xsegments); +} + +void +Blt_DrawArrowOld(Display *display, Drawable drawable, GC gc, int x, int y, int w, + int h, int borderWidth, int orientation) +{ + XPoint arrow[4]; + int s2, s; + int ax, ay; + +#define ARROW_IPAD 1 + w -= 2 * (ARROW_IPAD + borderWidth); + h -= 2 * (ARROW_IPAD + borderWidth); + x += ARROW_IPAD + borderWidth; + y += ARROW_IPAD + borderWidth; + + w |= 0x01; + h |= 0x01; + s = MIN(w, h); + s2 = s / 2; + ax = x + w / 2; + ay = y + h / 2; + + switch (orientation) { + case ARROW_UP: + ay -= s2/2 + 1; + arrow[2].x = arrow[0].x = ax; + arrow[2].y = arrow[0].y = ay; + arrow[0].x = ax + s2 + 1; + arrow[1].x = ax - s2; + arrow[0].y = arrow[1].y = ay + s2 + 1; + fprintf(stderr, "up arrow %d,%d %d,%d %d,%d\n", + arrow[0].x, arrow[0].y, + arrow[1].x, arrow[1].y, + arrow[2].x, arrow[2].y); + break; + case ARROW_DOWN: + ay -= s2/2; + arrow[3].x = arrow[0].x = ax; + arrow[3].y = arrow[0].y = ay + s2 + 1; + arrow[1].x = ax + s2 + 1; + arrow[2].x = ax - s2; + arrow[2].y = arrow[1].y = ay; + fprintf(stderr, "down arrow %d,%d %d,%d %d,%d\n", + arrow[0].x, arrow[0].y, + arrow[1].x, arrow[1].y, + arrow[2].x, arrow[2].y); + break; + case ARROW_LEFT: + ax -= s2 / 2; + arrow[3].x = arrow[0].x = ax; + arrow[3].y = arrow[0].y = ay; + arrow[1].y = ay - s2; + arrow[2].y = ay + s2 + 1; + arrow[2].x = arrow[1].x = ax + s2 + 1; + break; + case ARROW_RIGHT: + ax -= s2 / 2; + arrow[3].x = arrow[0].x = ax + s2 + 1; + arrow[3].y = arrow[0].y = ay; + arrow[1].y = ay - s2; + arrow[2].y = ay + s2; + arrow[2].x = arrow[1].x = ax; + break; + } + XFillPolygon(display, drawable, gc, arrow, 3, Convex, CoordModeOrigin); +} + +void +Blt_DrawArrow(Display *display, Drawable drawable, XColor *color, int x, int y, + int w, int h, int borderWidth, int orientation) +{ + int s; + int s2; + int ax, ay; + int dx, dy; + GC gc; + +#define ARROW_IPAD 1 + w -= 2 * (ARROW_IPAD + borderWidth); + h -= 2 * (ARROW_IPAD + borderWidth); + x += ARROW_IPAD + borderWidth; + y += ARROW_IPAD + borderWidth; + + s = MIN(w, h); + s2 = (s / 2) + 1; + ax = x + w / 2; + ay = y + h / 2; + + gc = Tk_GCForColor(color, drawable); + switch (orientation) { + case ARROW_UP: + ay -= s2 / 2; + for (dx = 0; dx < s2; dx++, ay++) { + XDrawLine(display, drawable, gc, ax - dx, ay, ax + dx, ay); + } + break; + case ARROW_DOWN: + ay += s2 / 2; + for (dx = 0; dx < s2; dx++, ay--) { + XDrawLine(display, drawable, gc, ax - dx, ay, ax + dx, ay); + } + break; + case ARROW_LEFT: + ax -= s2 / 2; + for (dy = 0; dy < s2; dy++, ax++) { + XDrawLine(display, drawable, gc, ax, ay - dy, ax, ay + dy); + } + break; + case ARROW_RIGHT: + ax += s2 / 2; + for (dy = 0; dy < s2; dy++, ax--) { + XDrawLine(display, drawable, gc, ax, ay - dy, ax, ay + dy); + } + break; + } +} + +long +Blt_MaxRequestSize(Display *display, size_t elemSize) +{ + static long maxSizeBytes = 0L; + + if (maxSizeBytes == 0L) { + long size; +#ifndef WIN32 + size = XExtendedMaxRequestSize(display); + if (size == 0) { + size = XMaxRequestSize(display); + } +#else + size = XMaxRequestSize(display); +#endif + size -= (4 * elemSize); + /* maxSizeBytes = (size * 4); */ + maxSizeBytes = size; + } + return (maxSizeBytes / elemSize); +} + +void +Blt_GetLineExtents(size_t nPoints, Point2d *points, Region2d *r) +{ + Point2d *p, *pend; + r->top = r->left = DBL_MAX; + r->bottom = r->right = -DBL_MAX; + for (p = points, pend = p + nPoints; p < pend; p++) { + if (r->top > p->y) { + r->top = p->y; + } + if (r->bottom < p->y) { + r->bottom = p->y; + } + if (r->left > p->x) { + r->left = p->x; + } + if (r->right < p->x) { + r->right = p->x; + } + } +} + +#undef Blt_Fill3DRectangle +void +Blt_Fill3DRectangle( + Tk_Window tkwin, /* Window for which border was allocated. */ + Drawable drawable, /* X window or pixmap in which to draw. */ + Tk_3DBorder border, /* Token for border to draw. */ + int x, int y, + int width, int height, /* Outside area of rectangular region. */ + int borderWidth, /* Desired width for border, in pixels. Border + * will be *inside* region. */ + int relief) /* Indicates 3D effect: TK_RELIEF_FLAT, + * TK_RELIEF_RAISED, or TK_RELIEF_SUNKEN. */ +{ +#ifndef notdef + if ((borderWidth > 1) && (width > 2) && (height > 2) && + ((relief == TK_RELIEF_SUNKEN) || (relief == TK_RELIEF_RAISED))) { + GC lightGC, darkGC; + int x2, y2; + + x2 = x + width - 1; + y2 = y + height - 1; +#define TK_3D_LIGHT2_GC TK_3D_DARK_GC+1 +#define TK_3D_DARK2_GC TK_3D_DARK_GC+2 + if (relief == TK_RELIEF_RAISED) { + lightGC = Tk_3DBorderGC(tkwin, border, TK_3D_FLAT_GC); + darkGC = Tk_3DBorderGC(tkwin, border, TK_3D_DARK_GC); +#ifdef notdef + darkGC = DefaultGC(Tk_Display(tkwin), Tk_ScreenNumber(tkwin)); +#endif + } else { + + lightGC = Tk_3DBorderGC(tkwin, border, TK_3D_LIGHT_GC); +#ifdef notdef + lightGC = DefaultGC(Tk_Display(tkwin), Tk_ScreenNumber(tkwin)); +#endif + darkGC = Tk_3DBorderGC(tkwin, border, TK_3D_FLAT_GC); + } + XDrawLine(Tk_Display(tkwin), drawable, lightGC, x, y, x2, y); + XDrawLine(Tk_Display(tkwin), drawable, darkGC, x2, y2, x2, y); + XDrawLine(Tk_Display(tkwin), drawable, darkGC, x2, y2, x, y2); + XDrawLine(Tk_Display(tkwin), drawable, lightGC, x, y, x, y2); + x++, y++, width -= 2, height -= 2, borderWidth--; + } +#endif + Tk_Fill3DRectangle(tkwin, drawable, border, x, y, width, height, + borderWidth, relief); +} + + +#undef Blt_Draw3DRectangle +void +Blt_Draw3DRectangle( + Tk_Window tkwin, /* Window for which border was allocated. */ + Drawable drawable, /* X window or pixmap in which to draw. */ + Tk_3DBorder border, /* Token for border to draw. */ + int x, int y, + int width, int height, /* Outside area of rectangular region. */ + int borderWidth, /* Desired width for border, in pixels. Border + * will be *inside* region. */ + int relief) /* Indicates 3D effect: TK_RELIEF_FLAT, + * TK_RELIEF_RAISED, or TK_RELIEF_SUNKEN. */ +{ +#ifndef notdef + if ((borderWidth > 1) && (width > 2) && (height > 2) && + ((relief == TK_RELIEF_SUNKEN) || (relief == TK_RELIEF_RAISED))) { + GC lightGC, darkGC; + int x2, y2; + + x2 = x + width - 1; + y2 = y + height - 1; + if (relief == TK_RELIEF_RAISED) { + lightGC = Tk_3DBorderGC(tkwin, border, TK_3D_FLAT_GC); + darkGC = Tk_3DBorderGC(tkwin, border, TK_3D_DARK_GC); +#ifdef notdef + darkGC = DefaultGC(Tk_Display(tkwin), Tk_ScreenNumber(tkwin)); +#endif + } else { + lightGC = Tk_3DBorderGC(tkwin, border, TK_3D_LIGHT_GC); +#ifdef notdef + lightGC = DefaultGC(Tk_Display(tkwin), Tk_ScreenNumber(tkwin)); +#endif + darkGC = Tk_3DBorderGC(tkwin, border, TK_3D_FLAT_GC); + } + XDrawLine(Tk_Display(tkwin), drawable, darkGC, x2, y2, x2, y); + XDrawLine(Tk_Display(tkwin), drawable, lightGC, x, y, x2, y); + XDrawLine(Tk_Display(tkwin), drawable, darkGC, x2, y2, x, y2); + XDrawLine(Tk_Display(tkwin), drawable, lightGC, x, y, x, y2); + x++, y++, width -= 2, height -= 2, borderWidth--; + } +#endif + Tk_Draw3DRectangle(tkwin, drawable, border, x, y, width, height, + borderWidth, relief); +} + +#ifdef notdef +typedef struct { + Screen *screen; + Visual *visual; + Colormap colormap; + Tk_Uid nameUid; +} BorderKey; + +typedef struct { + Screen *screen; /* Screen on which the border will be used. */ + Visual *visual; /* Visual for all windows and pixmaps using + * the border. */ + int depth; /* Number of bits per pixel of drawables where + * the border will be used. */ + Colormap colormap; /* Colormap out of which pixels are + * allocated. */ + int refCount; /* Number of active uses of this color (each + * active use corresponds to a call to + * Blt_Get3DBorder). If this count is 0, then + * this structure is no longer valid and it + * isn't present in borderTable: it is being + * kept around only because there are objects + * referring to it. The structure is freed + * when refCount is 0. */ + + XColor *bgColorPtr; /* Color of face. */ + XColor *shadows[4]; + + Pixmap darkStipple; /* Stipple pattern to use for drawing shadows + * areas. Used for displays with <= 64 colors + * or where colormap has filled up. */ + Pixmap lightStipple; /* Stipple pattern to use for drawing shadows + * areas. Used for displays with <= 64 colors + * or where colormap has filled up. */ + GC bgGC; /* Used (if necessary) to draw areas in the + * background color. */ + GC lightGC, darkGC; + Tcl_HashEntry *hashPtr; /* Entry in borderTable (needed in order to + * delete structure). */ + + struct _Blt_3DBorder *nextPtr; +} Border, *Blt_3DBorder; + + +void +Blt_Draw3DRectangle(tkwin, drawable, border, x, y, width, + height, borderWidth, relief) + Tk_Window tkwin; /* Window for which border was allocated. */ + Drawable drawable; /* X window or pixmap in which to draw. */ + Blt_3DBorder *borderPtr; /* Border to draw. */ + int x, y, width, height; /* Outside area of rectangular region. */ + int borderWidth; /* Desired width for border, in + * pixels. Border will be *inside* region. */ + int relief; /* Indicates 3D effect: TK_RELIEF_FLAT, + * TK_RELIEF_RAISED, or TK_RELIEF_SUNKEN. */ +{ + if ((width > (2 * borderWidth)) && (height > (2 * borderWidth))) { + int x2, y2; + int i; + + x2 = x + width - 1; + y2 = y + height - 1; + + XSetForeground(borderPtr->lightGC, borderPtr->shadows[0]); + XSetForeground(borderPtr->darkGC, borderPtr->shadows[3]); + XDrawLine(Tk_Display(tkwin), drawable, borderPtr->lightGC, + x, y, x2, y); + XDrawLine(Tk_Display(tkwin), drawable, borderPtr->lightGC, + x, y, x, y2); + XDrawLine(Tk_Display(tkwin), drawable, borderPtr->darkGC, + x2, y, x2, y2); + XDrawLine(Tk_Display(tkwin), drawable, borderPtr->darkGC, + x2, y2, x, y2); + XSetForeground(borderPtr->lightGC, borderPtr->shadows[1]); + XSetForeground(borderPtr->darkGC, borderPtr->shadows[2]); + for (i = 1; i < (borderWidth - 1); i++) { + + /* + * +---------- + * |+------- + * ||+----- + * ||| + * ||| + * || + * | + */ + x++, y++, x2--, y2--; + XDrawLine(Tk_Display(tkwin), drawable, borderPtr->lightGC, + x, y, x2, y); + XDrawLine(Tk_Display(tkwin), drawable, borderPtr->lightGC, + x, y, x, y2); + XDrawLine(Tk_Display(tkwin), drawable, borderPtr->darkGC, + x2, y, x2, y2); + XDrawLine(Tk_Display(tkwin), drawable, borderPtr->darkGC, + x2, y2, x, y2); + } + } +} + +void +Blt_Fill3DRectangle(tkwin, drawable, border, x, y, width, height, borderWidth, + relief) + Tk_Window tkwin; /* Window for which border was allocated. */ + Drawable drawable; /* X window or pixmap in which to draw. */ + Tk_3DBorder border; /* Token for border to draw. */ + int x, y, width, height; /* Outside area of rectangular region. */ + int borderWidth; /* Desired width for border, in + * pixels. Border will be *inside* region. */ + int relief; /* Indicates 3D effect: TK_RELIEF_FLAT, + * TK_RELIEF_RAISED, or TK_RELIEF_SUNKEN. */ +{ + Blt_3DBorder *borderPtr; + + XFillRectangle(Tk_Display(tkwin), drawable, borderPtr->bgGC, x, y, width, + height); + if ((borderWidth > 0) && (relief != BLT_RELIEF_FLAT)) { + Blt_Draw3DRectangle(tkwin, drawable, borderPtr, x, y, width, height, + borderWidth, relief); + } +} + + +void +FreeBorder(display, borderPtr) + Display *display; + Border *borderPtr; +{ + int i; + + if (borderPtr->bgColorPtr != NULL) { + Tk_FreeColor(display, borderPtr->bgColorPtr); + } + for (i = 0; i < 4; i++) { + Tk_FreeColor(display, borderPtr->shadows[i]); + } + if (borderPtr->darkGC != NULL) { + Blt_FreePrivateGC(display, borderPtr->darkGC); + } + if (borderPtr->lightGC != NULL) { + Blt_FreePrivateGC(tkwin, borderPtr->lightGC); + } + if (borderPtr->bgGC != NULL) { + Blt_FreePrivateGC(tkwin, borderPtr->bgGC); + } + Blt_Free(borderPtr); +} + +void +Blt_Free3DBorder(display, border) + Display *display; + Blt_3DBorder border; +{ + Border *borderPtr = (Border *)border; + + borderPtr->refCount--; + if (borderPtr->refCount >= 0) { + /* Search for the border in the bucket. Start at the head. */ + headPtr = Blt_GetHashValue(borderPtr->hashPtr); + lastPtr = NULL; + while ((headPtr != borderPtr) && (headPtr != NULL)) { + lastPtr = headPtr; + headPtr = headPtr->next; + } + if (headPtr == NULL) { + return; /* This can't happen. It means that we could + * not find the border. */ + } + if (lastPtr != NULL) { + lastPtr->next = borderPtr->next; + } else { + Tcl_DeleteHashEntry(borderPtr->hashPtr); + } + FreeBorder(display, borderPtr); + } +} + +Blt_3DBorder * +Blt_Get3DBorder(Tcl_Interp *interp, Tk_Window tkwin, const char *borderName) +{ + Blt_3DBorder *borderPtr, *lastBorderPtr; + Blt_HashEntry *hPtr; + XColor *bgColorPtr; + char **argv; + const char *colorName; + int argc; + int isNew; + + lastBorderPtr = NULL; + hPtr = Tcl_CreateHashEntry(&dataPtr->borderTable, borderName, &isNew); + if (!isNew) { + borderPtr = lastBorderPtr = Blt_GetHashValue(hPtr); + while (borderPtr != NULL) { + if ((Tk_Screen(tkwin) == borderPtr->screen) && + (Tk_Colormap(tkwin) == borderPtr->colormap)) { + borderPtr->refCount++; + return borderPtr; + } + borderPtr = borderPtr->nextPtr; + } + } + /* Create a new border. */ + argv = NULL; + bgColorPtr = NULL; + + if (Tcl_SplitList(interp, borderName, &argc, &argv) != TCL_OK) { + goto error; + } + colorName = borderName; + bgColorPtr = Tk_GetColor(interp, tkwin, colorName); + if (bgColorPtr == NULL) { + goto error; + } + + /* Create a new border */ + borderPtr = Blt_AssertCalloc(1, sizeof(Blt_3DBorder)); + borderPtr->screen = Tk_Screen(tkwin); + borderPtr->visual = Tk_Visual(tkwin); + borderPtr->depth = Tk_Depth(tkwin); + borderPtr->colormap = Tk_Colormap(tkwin); + borderPtr->refCount = 1; + borderPtr->bgColorPtr = bgColorPtr; + borderPtr->darkGC = Blt_GetPrivateGC(tkwin, 0, NULL); + borderPtr->lightGC = Blt_GetPrivateGC(tkwin, 0, NULL); + borderPtr->hashPtr = lastBorderPtr->hashPtr; + lastBorderPtr->nextPtr = lastBorderPtr; + { + HSV hsv; + XColor color; + double sat, sat0, diff, step, hstep; + int count; + + /* Convert the face (background) color to HSV */ + Blt_XColorToHSV(borderPtr->bgColorPtr, &hsv); + + /* Using the color as the baseline intensity, pick a set of colors + * around the intensity. */ +#define UFLOOR(x,u) (floor((x)*(u))/(u)) + diff = hsv.sat - UFLOOR(hsv.sat, 0.2); + sat = 0.1 + (diff - 0.1); + sat0 = hsv.sat; + count = 0; + for (sat = 0.1 + (diff - 0.1); sat <= 1.0; sat += 0.2) { + if (FABS(sat0 - sat) >= 0.1) { + hsv.sat = sat; + Blt_HSVToXColor(&hsv, &color); + borderPtr->shadows[count] = Tk_GetColorByValue(tkwin, &color); + count++; + } + } + } + Blt_SetHashValue(hPtr, borderPtr); + if (argv != NULL) { + Blt_Free(argv); + } + return TCL_OK; + + error: + if (argv != NULL) { + Blt_Free(argv); + } + if (bgColorPtr != NULL) { + Tk_FreeColor(bgColorPtr); + } + if (isNew) { + Blt_DeleteHashEntry(&borderTable, hPtr); + } + return NULL; +} + + +#endif + +typedef struct { + float x, y, z; +} Vector3f; + +typedef struct { + float x, y, z, w; +} Vector4f; + +typedef Vector4f Quaternion; + +typedef float Matrix3x3f[3][3]; + +typedef struct _ArcBall { + Vector3f click; + Vector3f drag; + float xScale; + float yScale; +} ArcBall; + +/* + * Arcball sphere constants: + * Diameter is 2.0f + * Radius is 1.0f + * Radius squared is 1.0f + */ + +/* assuming IEEE-754 (float), which i believe has max precision of 7 bits */ +#define EPSILON FLT_EPSILON +static INLINE float +LengthVector3f(Vector3f *a) +{ + return sqrtf((a->x * a->x) + (a->y * a->y) + (a->z * a->z)); +} + +static INLINE float DotProductVector3f(Vector3f *a, Vector3f *b) +{ + return (a->x * b->x) + (a->y * b->y) + (a->z * b->z); +} + +/** + * Calculate the cross product of two 3D vectors: c = a x b. + * "c" must not refer to the same memory location as "a" or "b". + */ +static INLINE void +CrossProductVector3f(Vector3f *a, Vector3f *b, Vector3f *c) +{ + c->x = (a->y * b->z) - (b->y * a->z); + c->y = (a->z * b->x) - (b->z * a->x); + c->z = (a->x * b->y) - (b->x * a->y); +} + +static void +PointOnSphere (ArcBall *arcPtr, float x, float y, Vector3f *outPtr) +{ + float sx, sy; + float d; + + /* Adjust point coords and scale down to range of [-1 ... 1] */ + sx = (x * arcPtr->xScale) - 1.0f; + sy = 1.0f - (y * arcPtr->yScale); + + /* Compute the square of the length of the vector to the point from the + * center. */ + d = (sx * sx) + (sy * sy); + + /* If the point is mapped outside of the sphere ... + * (length > radius squared) + */ + if (d > 1.0f) { + float scale; + + /* Compute a normalizing factor (radius / sqrt(length)) */ + scale = 1.0f / sqrt (d); + + /* Return the "normalized" vector, a point on the sphere */ + outPtr->x = sx * scale; + outPtr->y = sy * scale; + outPtr->z = 0.0f; + } else { /* else it's on the inside */ + /* Return a vector to a point mapped inside the sphere + * sqrt(radius squared - length) */ + outPtr->x = sx; + outPtr->y = sy; + outPtr->z = sqrtf(1.0f - d); + } +} + +static void +SetArcBallBounds(ArcBall *arcPtr, float w, float h) +{ + if (w <= 1.0f ) { + w = 2.0f; + } + if (h <= 1.0f ) { + h = 2.0f; + } + /* Set adjustment factor for width/height */ + arcPtr->xScale = 1.0f / ((w - 1.0f) * 0.5f); + arcPtr->yScale = 1.0f / ((h - 1.0f) * 0.5f); +} + +static ArcBall * +CreateArcBall (float w, float h) +{ + ArcBall *arcPtr; + + arcPtr = Blt_AssertCalloc(1, sizeof(ArcBall)); + SetArcBallBounds (arcPtr, w, h); + return arcPtr; +} + +static void +DestroyArcBall(ArcBall *arcPtr) +{ + if (arcPtr != NULL) { + Blt_Free(arcPtr); + } +} + +/* Mouse down: Supply mouse position in x and y */ +static void +ArcBallOnClick(ArcBall *arcPtr, float x, float y) +{ + PointOnSphere (arcPtr, x, y, &arcPtr->click); +} + +/* Mouse drag, calculate rotation: Supply mouse position in x and y */ +static void +ArcBallOnDrag(ArcBall *arcPtr, float x, float y, Quaternion *outPtr) +{ + /* Map the point to the sphere */ + PointOnSphere(arcPtr, x, y, &arcPtr->drag); + + /* Return the quaternion equivalent to the rotation */ + if (outPtr != NULL) { + Vector3f perp; + + /* Compute the vector perpendicular to the begin and end vectors */ + CrossProductVector3f(&arcPtr->drag, &arcPtr->click, &perp); + + /* Compute the length of the perpendicular vector */ + if (LengthVector3f(&perp) > FLT_EPSILON) { + /* If its non-zero, we're ok, so return the perpendicular + * vector as the transform after all + */ + outPtr->x = perp.x; + outPtr->y = perp.y; + outPtr->z = perp.z; + /* In the quaternion values, w is cosine (theta / 2), + * where theta is rotation angle + */ + outPtr->w = DotProductVector3f(&arcPtr->click, &arcPtr->drag); + } else { + /* If it is zero, the begin and end vectors coincide, + * so return an identity transform + */ + outPtr->x = outPtr->y = outPtr->z = outPtr->z = 0.0f; + } + } +} + + +static INLINE void +SetRotationMatrix(const Quaternion* q, Matrix3x3f mat) +{ + float n, s; + float xs, ys, zs; + float wx, wy, wz; + float xx, xy, xz; + float yy, yz, zz; + + assert(mat && q); + + n = (q->x * q->x) + (q->y * q->y) + (q->z * q->z) + (q->w * q->w); + + s = (n > 0.0f) ? (2.0f / n) : 0.0f; + + xs = q->x * s; + ys = q->y * s; + zs = q->z * s; + wx = q->w * xs; + wy = q->w * ys; + wz = q->w * zs; + xx = q->x * xs; + xy = q->x * ys; + xz = q->x * zs; + yy = q->y * ys; + yz = q->y * zs; + zz = q->z * zs; + + mat[0][0] = 1.0f - (yy + zz); + mat[0][1] = xy - wz; + mat[0][2] = xz + wy; + mat[1][0] = xy + wz; + mat[1][1] = 1.0f - (xx + zz); + mat[1][2] = yz - wx; + mat[2][0] = xz - wy; + mat[2][1] = yz + wx; + mat[2][2] = 1.0f - (xx + yy); +} + +/* Return quaternion product qL * qR. Note: order is important! + * To combine rotations, use the product Mul(Second, First), + * which gives the effect of rotating by First then Second. */ +static INLINE void +CombineRotations(Quaternion *a, Quaternion *b, Quaternion *result) +{ + result->w = (a->w * b->w) - (a->x * b->x) - (a->y * b->y) - (a->z * b->z); + result->x = (a->w * b->x) + (a->x * b->w) + (a->y * b->z) - (a->z * b->y); + result->y = (a->w * b->y) + (a->y * b->w) + (a->z * b->x) - (a->x * b->z); + result->z = (a->w * b->z) + (a->z * b->w) + (a->x * b->y) - (a->y * b->x); +} + + +static int +GetQuaternionFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, Quaternion *q) +{ + Tcl_Obj **objv; + int objc; + double x, y, z, w; + + if (Tcl_ListObjGetElements(interp, objPtr, &objc, &objv) != TCL_OK) { + return TCL_ERROR; + } + if (objc != 4) { + Tcl_AppendResult(interp, "wrong number of elements in quaternion \"", + Tcl_GetString(objPtr), "\"", (char *)NULL); + return TCL_ERROR; + } + if ((Tcl_GetDoubleFromObj(interp, objv[0], &x) != TCL_OK) || + (Tcl_GetDoubleFromObj(interp, objv[1], &y) != TCL_OK) || + (Tcl_GetDoubleFromObj(interp, objv[2], &z) != TCL_OK) || + (Tcl_GetDoubleFromObj(interp, objv[3], &w) != TCL_OK)) { + return TCL_ERROR; + } + q->x = x, q->y = y, q->z = z, q->w = w; + return TCL_OK; +} + +static int +ArcBallCombineOp(ClientData clientData, Tcl_Interp *interp, int objc, + Tcl_Obj *const *objv) +{ + Tcl_Obj *listObjPtr; + Quaternion q1, q2, r; + + if (GetQuaternionFromObj(interp, objv[2], &q1) != TCL_OK) { + return TCL_ERROR; + } + if (GetQuaternionFromObj(interp, objv[3], &q2) != TCL_OK) { + return TCL_ERROR; + } + CombineRotations(&q2, &q1, &r); + listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **) NULL); + Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewDoubleObj(r.x)); + Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewDoubleObj(r.y)); + Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewDoubleObj(r.w)); + Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewDoubleObj(r.z)); + Tcl_SetObjResult(interp, listObjPtr); + return TCL_OK; +} + +static int +ArcBallRotateOp(ClientData clientData, Tcl_Interp *interp, int objc, + Tcl_Obj *const *objv) +{ + ArcBall *arcPtr; + Tcl_Obj *listObjPtr; + Quaternion q; + double x1, y1, x2, y2; + int w, h; + + if ((Tcl_GetIntFromObj(interp, objv[2], &w) != TCL_OK) || + (Tcl_GetIntFromObj(interp, objv[3], &h) != TCL_OK)) { + return TCL_ERROR; + } + if ((Tcl_GetDoubleFromObj(interp, objv[4], &x1) != TCL_OK) || + (Tcl_GetDoubleFromObj(interp, objv[5], &y1) != TCL_OK) || + (Tcl_GetDoubleFromObj(interp, objv[6], &x2) != TCL_OK) || + (Tcl_GetDoubleFromObj(interp, objv[7], &y2) != TCL_OK)) { + return TCL_ERROR; + } + arcPtr = CreateArcBall((float)w, (float)h); + ArcBallOnClick(arcPtr, x1, y1); + ArcBallOnDrag(arcPtr, x2, y2, &q); + DestroyArcBall(arcPtr); + listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **) NULL); + Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewDoubleObj(q.x)); + Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewDoubleObj(q.y)); + Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewDoubleObj(q.w)); + Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewDoubleObj(q.z)); + Tcl_SetObjResult(interp, listObjPtr); + return TCL_OK; +} + +static int +ArcBallMatrixOp(ClientData clientData, Tcl_Interp *interp, int objc, + Tcl_Obj *const *objv) +{ + Matrix3x3f rot; + Tcl_Obj *listObjPtr; + Quaternion q; + + if (GetQuaternionFromObj(interp, objv[2], &q) != TCL_OK) { + return TCL_ERROR; + } + SetRotationMatrix(&q, rot); + listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **) NULL); + Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewDoubleObj(rot[0][0])); + Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewDoubleObj(rot[0][1])); + Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewDoubleObj(rot[0][2])); + Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewDoubleObj(rot[1][0])); + Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewDoubleObj(rot[1][1])); + Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewDoubleObj(rot[1][2])); + Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewDoubleObj(rot[2][0])); + Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewDoubleObj(rot[2][1])); + Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewDoubleObj(rot[2][2])); + Tcl_SetObjResult(interp, listObjPtr); + return TCL_OK; +} + +static Blt_OpSpec arcBallOps[] = +{ + {"combine", 1, ArcBallCombineOp, 4, 4, "quat1 quat2",}, + {"matrix", 1, ArcBallMatrixOp, 3, 3, "quat",}, + {"rotate", 1, ArcBallRotateOp, 8, 8, "w h x1 y1 x2 y2",}, +}; +static int nArcBallOps = sizeof(arcBallOps) / sizeof(Blt_OpSpec); + +static int +ArcBallCmd(ClientData clientData, Tcl_Interp *interp, int objc, + Tcl_Obj *const *objv) +{ + Tcl_ObjCmdProc *proc; + + proc = Blt_GetOpFromObj(interp, nArcBallOps, arcBallOps, BLT_OP_ARG1, + objc, objv, 0); + if (proc == NULL) { + return TCL_ERROR; + } + return (*proc) (clientData, interp, objc, objv); +} + +#undef Tk_GetPixmap +Pixmap +Blt_GetPixmap(Display *display, Drawable drawable, int w, int h, int depth, + int lineNum, const char *fileName) +{ + if (w <= 0) { + fprintf(stderr, "line %d of %s: width is %d\n", + lineNum, fileName, w); + abort(); + } + if (h <= 0) { + fprintf(stderr, "line %d of %s: height is %d\n", + lineNum, fileName, h); + abort(); + } + return Tk_GetPixmap(display, drawable, w, h, depth); +} diff --git a/src/bltGrPSOutput.C b/src/bltGrPSOutput.C new file mode 100644 index 0000000..44cfdb8 --- /dev/null +++ b/src/bltGrPSOutput.C @@ -0,0 +1,1697 @@ + +/* + * bltPs.c -- + * + * This module implements general PostScript conversion routines. + * + * Copyright 1991-2004 George A Howlett. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include "bltInt.h" +#include <stdarg.h> +#include <X11/Xutil.h> +#include <X11/Xatom.h> +#include "tkIntBorder.h" +#include "bltDBuffer.h" +#include "bltPicture.h" +#include "bltPsInt.h" +#include "tkDisplay.h" +#include "tkFont.h" + +#define PS_MAXPATH 1500 /* Maximum number of components in a + * PostScript (level 1) path. */ + +#define PICA_MM 2.83464566929 +#define PICA_INCH 72.0 +#define PICA_CM 28.3464566929 + +static Tcl_Interp *psInterp = NULL; + +/* + *--------------------------------------------------------------------------- + * + * Blt_Ps_GetPica -- + * + * Given a string, returns the number of pica corresponding to that + * string. + * + * Results: + * The return value is a standard TCL return result. If TCL_OK is + * returned, then everything went well and the pixel distance is stored + * at *doublePtr; otherwise TCL_ERROR is returned and an error message is + * left in interp->result. + * + * Side effects: + * None. + * + *--------------------------------------------------------------------------- + */ +int +Blt_Ps_GetPicaFromObj( + Tcl_Interp *interp, /* Use this for error reporting. */ + Tcl_Obj *objPtr, /* String describing a number of + * pixels. */ + int *picaPtr) /* Place to store converted result. */ +{ + char *p; + double pica; + const char *string; + + string = Tcl_GetString(objPtr); + pica = strtod((char *)string, &p); + if (p == string) { + goto error; + } + if (pica < 0.0) { + goto error; + } + while ((*p != '\0') && isspace(UCHAR(*p))) { + p++; + } + switch (*p) { + case '\0': break; + case 'c': pica *= PICA_CM; p++; break; + case 'i': pica *= PICA_INCH; p++; break; + case 'm': pica *= PICA_MM; p++; break; + case 'p': p++; break; + default: goto error; + } + while ((*p != '\0') && isspace(UCHAR(*p))) { + p++; + } + if (*p == '\0') { + *picaPtr = ROUND(pica); + return TCL_OK; + } + error: + Tcl_AppendResult(interp, "bad screen distance \"", string, "\"", + (char *) NULL); + return TCL_ERROR; +} + +int +Blt_Ps_GetPadFromObj( + Tcl_Interp *interp, /* Interpreter to send results back + * to */ + Tcl_Obj *objPtr, /* Pixel value string */ + Blt_Pad *padPtr) +{ + int side1, side2; + int objc; + Tcl_Obj **objv; + + if (Tcl_ListObjGetElements(interp, objPtr, &objc, &objv) != TCL_OK) { + return TCL_ERROR; + } + if ((objc < 1) || (objc > 2)) { + Tcl_AppendResult(interp, "wrong # elements in padding list", + (char *)NULL); + return TCL_ERROR; + } + if (Blt_Ps_GetPicaFromObj(interp, objv[0], &side1) != TCL_OK) { + return TCL_ERROR; + } + side2 = side1; + if ((objc > 1) && + (Blt_Ps_GetPicaFromObj(interp, objv[1], &side2) != TCL_OK)) { + return TCL_ERROR; + } + /* Don't update the pad structure until we know both values are okay. */ + padPtr->side1 = side1; + padPtr->side2 = side2; + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_Ps_ComputeBoundingBox -- + * + * Computes the bounding box for the PostScript plot. First get the size + * of the plot (by default, it's the size of graph's X window). If the + * plot plus the page border is bigger than the designated paper size, or + * if the "-maxpect" option is turned on, scale the plot to the page. + * + * Note: All coordinates/sizes are in screen coordinates, not PostScript + * coordinates. This includes the computed bounding box and paper + * size. They will be scaled to printer points later. + * + * Results: + * Returns the height of the paper in screen coordinates. + * + * Side Effects: + * The graph dimensions (width and height) are changed to the requested + * PostScript plot size. + * + *--------------------------------------------------------------------------- + */ +int +Blt_Ps_ComputeBoundingBox(PageSetup *setupPtr, int width, int height) +{ + int paperWidth, paperHeight; + int x, y, hSize, vSize, hBorder, vBorder; + float hScale, vScale, scale; + + x = setupPtr->padLeft; + y = setupPtr->padTop; + + hBorder = PADDING(setupPtr->xPad); + vBorder = PADDING(setupPtr->yPad); + + if (setupPtr->flags & PS_LANDSCAPE) { + hSize = height; + vSize = width; + } else { + hSize = width; + vSize = height; + } + /* + * If the paper size wasn't specified, set it to the graph size plus the + * paper border. + */ + paperWidth = (setupPtr->reqPaperWidth > 0) ? setupPtr->reqPaperWidth : + hSize + hBorder; + paperHeight = (setupPtr->reqPaperHeight > 0) ? setupPtr->reqPaperHeight : + vSize + vBorder; + + /* + * Scale the plot size (the graph itself doesn't change size) if it's + * bigger than the paper or if -maxpect was set. + */ + hScale = vScale = 1.0f; + if ((setupPtr->flags & PS_MAXPECT) || ((hSize + hBorder) > paperWidth)) { + hScale = (float)(paperWidth - hBorder) / (float)hSize; + } + if ((setupPtr->flags & PS_MAXPECT) || ((vSize + vBorder) > paperHeight)) { + vScale = (float)(paperHeight - vBorder) / (float)vSize; + } + scale = MIN(hScale, vScale); + if (scale != 1.0f) { + hSize = (int)((hSize * scale) + 0.5f); + vSize = (int)((vSize * scale) + 0.5f); + } + setupPtr->scale = scale; + if (setupPtr->flags & PS_CENTER) { + if (paperWidth > hSize) { + x = (paperWidth - hSize) / 2; + } + if (paperHeight > vSize) { + y = (paperHeight - vSize) / 2; + } + } + setupPtr->left = x; + setupPtr->bottom = y; + setupPtr->right = x + hSize - 1; + setupPtr->top = y + vSize - 1; + setupPtr->paperHeight = paperHeight; + setupPtr->paperWidth = paperWidth; + return paperHeight; +} + +PostScript * +Blt_Ps_Create(Tcl_Interp *interp, PageSetup *setupPtr) +{ + PostScript *psPtr; + + psPtr = Blt_AssertMalloc(sizeof(PostScript)); + psPtr->setupPtr = setupPtr; + psPtr->interp = interp; + Tcl_DStringInit(&psPtr->dString); + return psPtr; +} + +void +Blt_Ps_SetPrinting(PostScript *psPtr, int state) +{ + psInterp = ((state) && (psPtr != NULL)) ? psPtr->interp : NULL; +} + +int +Blt_Ps_IsPrinting(void) +{ + return (psInterp != NULL); +} + +void +Blt_Ps_Free(PostScript *psPtr) +{ + Tcl_DStringFree(&psPtr->dString); + Blt_Free(psPtr); +} + +const char * +Blt_Ps_GetValue(PostScript *psPtr, int *lengthPtr) +{ + *lengthPtr = strlen(Tcl_DStringValue(&psPtr->dString)); + return Tcl_DStringValue(&psPtr->dString); +} + +void +Blt_Ps_SetInterp(PostScript *psPtr, Tcl_Interp *interp) +{ + Tcl_DStringResult(interp, &psPtr->dString); +} + +char * +Blt_Ps_GetScratchBuffer(PostScript *psPtr) +{ + return psPtr->scratchArr; +} + +Tcl_Interp * +Blt_Ps_GetInterp(PostScript *psPtr) +{ + return psPtr->interp; +} + +Tcl_DString * +Blt_Ps_GetDString(PostScript *psPtr) +{ + return &psPtr->dString; +} + +int +Blt_Ps_SaveFile(Tcl_Interp *interp, PostScript *psPtr, const char *fileName) +{ + Tcl_Channel channel; + int nWritten, nBytes; + char *bytes; + + channel = Tcl_OpenFileChannel(interp, fileName, "w", 0660); + if (channel == NULL) { + return TCL_ERROR; + } + nBytes = Tcl_DStringLength(&psPtr->dString); + bytes = Tcl_DStringValue(&psPtr->dString); + nWritten = Tcl_Write(channel, bytes, nBytes); + Tcl_Close(interp, channel); + if (nWritten != nBytes) { + Tcl_AppendResult(interp, "short file \"", fileName, (char *)NULL); + Tcl_AppendResult(interp, "\" : wrote ", Blt_Itoa(nWritten), " of ", + (char *)NULL); + Tcl_AppendResult(interp, Blt_Itoa(nBytes), " bytes.", (char *)NULL); + return TCL_ERROR; + } + return TCL_OK; +} + +void +Blt_Ps_VarAppend +TCL_VARARGS_DEF(PostScript *, arg1) +{ + va_list argList; + PostScript *psPtr; + const char *string; + + psPtr = TCL_VARARGS_START(PostScript, arg1, argList); + for (;;) { + string = va_arg(argList, char *); + if (string == NULL) { + break; + } + Tcl_DStringAppend(&psPtr->dString, string, -1); + } +} + +void +Blt_Ps_AppendBytes(PostScript *psPtr, const char *bytes, int length) +{ + Tcl_DStringAppend(&psPtr->dString, bytes, length); +} + +void +Blt_Ps_Append(PostScript *psPtr, const char *string) +{ + Tcl_DStringAppend(&psPtr->dString, string, -1); +} + +void +Blt_Ps_Format +TCL_VARARGS_DEF(PostScript *, arg1) +{ + va_list argList; + PostScript *psPtr; + char *fmt; + + psPtr = TCL_VARARGS_START(PostScript, arg1, argList); + fmt = va_arg(argList, char *); + vsnprintf(psPtr->scratchArr, POSTSCRIPT_BUFSIZ, fmt, argList); + va_end(argList); + Tcl_DStringAppend(&psPtr->dString, psPtr->scratchArr, -1); +} + +int +Blt_Ps_IncludeFile(Tcl_Interp *interp, Blt_Ps ps, const char *fileName) +{ + Tcl_Channel channel; + Tcl_DString dString; + char *buf; + char *libDir; + int nBytes; + + buf = Blt_Ps_GetScratchBuffer(ps); + + /* + * Read in a standard prolog file from file and append it to the + * PostScript output stored in the Tcl_DString in psPtr. + */ + + libDir = (char *)Tcl_GetVar(interp, "blt_library", TCL_GLOBAL_ONLY); + if (libDir == NULL) { + Tcl_AppendResult(interp, "couldn't find BLT script library:", + "global variable \"blt_library\" doesn't exist", (char *)NULL); + return TCL_ERROR; + } + Tcl_DStringInit(&dString); + Tcl_DStringAppend(&dString, libDir, -1); + Tcl_DStringAppend(&dString, "/", -1); + Tcl_DStringAppend(&dString, fileName, -1); + fileName = Tcl_DStringValue(&dString); + Blt_Ps_VarAppend(ps, "\n% including file \"", fileName, "\"\n\n", + (char *)NULL); + channel = Tcl_OpenFileChannel(interp, fileName, "r", 0); + if (channel == NULL) { + Tcl_AppendResult(interp, "couldn't open prologue file \"", fileName, + "\": ", Tcl_PosixError(interp), (char *)NULL); + return TCL_ERROR; + } + for(;;) { + nBytes = Tcl_Read(channel, buf, POSTSCRIPT_BUFSIZ); + if (nBytes < 0) { + Tcl_AppendResult(interp, "error reading prologue file \"", + fileName, "\": ", Tcl_PosixError(interp), + (char *)NULL); + Tcl_Close(interp, channel); + Tcl_DStringFree(&dString); + return TCL_ERROR; + } + if (nBytes == 0) { + break; + } + buf[nBytes] = '\0'; + Blt_Ps_Append(ps, buf); + } + Tcl_DStringFree(&dString); + Tcl_Close(interp, channel); + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * XColorToPostScript -- + * + * Convert the a XColor (from its RGB values) to a PostScript command. + * If a Tk color map variable exists, it will be consulted for a + * PostScript translation based upon the color name. + * + * Maps an X color intensity (0 to 2^16-1) to a floating point value + * [0..1]. Many versions of Tk don't properly handle the the lower 8 + * bits of the color intensity, so we can only consider the upper 8 bits. + * + * Results: + * The string representing the color mode is returned. + * + *--------------------------------------------------------------------------- + */ +static void +XColorToPostScript(Blt_Ps ps, XColor *colorPtr) +{ + /* + * Shift off the lower byte before dividing because some versions of Tk + * don't fill the lower byte correctly. + */ + Blt_Ps_Format(ps, "%g %g %g", + ((double)(colorPtr->red >> 8) / 255.0), + ((double)(colorPtr->green >> 8) / 255.0), + ((double)(colorPtr->blue >> 8) / 255.0)); +} + +void +Blt_Ps_XSetBackground(PostScript *psPtr, XColor *colorPtr) +{ + /* If the color name exists in TCL array variable, use that translation */ + if ((psPtr->setupPtr != NULL) && (psPtr->setupPtr->colorVarName != NULL)) { + const char *psColor; + + psColor = Tcl_GetVar2(psPtr->interp, psPtr->setupPtr->colorVarName, + Tk_NameOfColor(colorPtr), 0); + if (psColor != NULL) { + Blt_Ps_VarAppend(psPtr, " ", psColor, "\n", (char *)NULL); + return; + } + } + XColorToPostScript(psPtr, colorPtr); + Blt_Ps_Append(psPtr, " setrgbcolor\n"); + if (psPtr->setupPtr->flags & PS_GREYSCALE) { + Blt_Ps_Append(psPtr, " currentgray setgray\n"); + } +} + +void +Blt_Ps_XSetForeground(PostScript *psPtr, XColor *colorPtr) +{ + /* If the color name exists in TCL array variable, use that translation */ + if ((psPtr->setupPtr != NULL) && (psPtr->setupPtr->colorVarName != NULL)) { + const char *psColor; + + psColor = Tcl_GetVar2(psPtr->interp, psPtr->setupPtr->colorVarName, + Tk_NameOfColor(colorPtr), 0); + if (psColor != NULL) { + Blt_Ps_VarAppend(psPtr, " ", psColor, "\n", (char *)NULL); + return; + } + } + XColorToPostScript(psPtr, colorPtr); + Blt_Ps_Append(psPtr, " setrgbcolor\n"); + if (psPtr->setupPtr->flags & PS_GREYSCALE) { + Blt_Ps_Append(psPtr, " currentgray setgray\n"); + } +} + +/* + *--------------------------------------------------------------------------- + * + * ReverseBits -- + * + * Convert a byte from a X image into PostScript image order. This + * requires not only the nybbles to be reversed but also their bit + * values. + * + * Results: + * The converted byte is returned. + * + *--------------------------------------------------------------------------- + */ +INLINE static unsigned char +ReverseBits(unsigned char byte) +{ + byte = ((byte >> 1) & 0x55) | ((byte << 1) & 0xaa); + byte = ((byte >> 2) & 0x33) | ((byte << 2) & 0xcc); + byte = ((byte >> 4) & 0x0f) | ((byte << 4) & 0xf0); + return byte; +} + +/* + *--------------------------------------------------------------------------- + * + * ByteToHex -- + * + * Convert a byte to its ASCII hexidecimal equivalent. + * + * Results: + * The converted 2 ASCII character string is returned. + * + *--------------------------------------------------------------------------- + */ +INLINE static void +ByteToHex(unsigned char byte, char *string) +{ + static char hexDigits[] = "0123456789ABCDEF"; + + string[0] = hexDigits[byte >> 4]; + string[1] = hexDigits[byte & 0x0F]; +} + +#ifdef WIN32 +/* + *--------------------------------------------------------------------------- + * + * Blt_Ps_XSetBitmapData -- + * + * Output a PostScript image string of the given bitmap image. It is + * assumed the image is one bit deep and a zero value indicates an + * off-pixel. To convert to PostScript, the bits need to be reversed + * from the X11 image order. + * + * Results: + * None. + * + * Side Effects: + * The PostScript image string is appended. + * + *--------------------------------------------------------------------------- + */ +void +Blt_Ps_XSetBitmapData( + PostScript *psPtr, + Display *display, + Pixmap bitmap, + int width, int height) +{ + unsigned char byte; + int x, y, bitPos; + unsigned long pixel; + int byteCount; + char string[10]; + unsigned char *srcBits, *srcPtr; + int bytesPerRow; + + srcBits = Blt_GetBitmapData(display, bitmap, width, height, &bytesPerRow); + if (srcBits == NULL) { + OutputDebugString("Can't get bitmap data"); + return; + } + Blt_Ps_Append(psPtr, "\t<"); + byteCount = bitPos = 0; /* Suppress compiler warning */ + for (y = height - 1; y >= 0; y--) { + srcPtr = srcBits + (bytesPerRow * y); + byte = 0; + for (x = 0; x < width; x++) { + bitPos = x % 8; + pixel = (*srcPtr & (0x80 >> bitPos)); + if (pixel) { + byte |= (unsigned char)(1 << bitPos); + } + if (bitPos == 7) { + byte = ReverseBits(byte); + ByteToHex(byte, string); + string[2] = '\0'; + byteCount++; + srcPtr++; + byte = 0; + if (byteCount >= 30) { + string[2] = '\n'; + string[3] = '\t'; + string[4] = '\0'; + byteCount = 0; + } + Blt_Ps_Append(psPtr, string); + } + } /* x */ + if (bitPos != 7) { + byte = ReverseBits(byte); + ByteToHex(byte, string); + string[2] = '\0'; + Blt_Ps_Append(psPtr, string); + byteCount++; + } + } /* y */ + Blt_Free(srcBits); + Blt_Ps_Append(psPtr, ">\n"); +} + +#else + +/* + *--------------------------------------------------------------------------- + * + * Blt_Ps_XSetBitmapData -- + * + * Output a PostScript image string of the given bitmap image. It is + * assumed the image is one bit deep and a zero value indicates an + * off-pixel. To convert to PostScript, the bits need to be reversed + * from the X11 image order. + * + * Results: + * None. + * + * Side Effects: + * The PostScript image string is appended to interp->result. + * + *--------------------------------------------------------------------------- + */ +void +Blt_Ps_XSetBitmapData(Blt_Ps ps, Display *display, Pixmap bitmap, int w, int h) +{ + XImage *imagePtr; + int byteCount; + int y, bitPos; + + imagePtr = XGetImage(display, bitmap, 0, 0, w, h, 1, ZPixmap); + Blt_Ps_Append(ps, "\t<"); + byteCount = bitPos = 0; /* Suppress compiler warning */ + for (y = 0; y < h; y++) { + unsigned char byte; + char string[10]; + int x; + + byte = 0; + for (x = 0; x < w; x++) { + unsigned long pixel; + + pixel = XGetPixel(imagePtr, x, y); + bitPos = x % 8; + byte |= (unsigned char)(pixel << bitPos); + if (bitPos == 7) { + byte = ReverseBits(byte); + ByteToHex(byte, string); + string[2] = '\0'; + byteCount++; + byte = 0; + if (byteCount >= 30) { + string[2] = '\n'; + string[3] = '\t'; + string[4] = '\0'; + byteCount = 0; + } + Blt_Ps_Append(ps, string); + } + } /* x */ + if (bitPos != 7) { + byte = ReverseBits(byte); + ByteToHex(byte, string); + string[2] = '\0'; + Blt_Ps_Append(ps, string); + byteCount++; + } + } /* y */ + Blt_Ps_Append(ps, ">\n"); + XDestroyImage(imagePtr); +} + +#endif /* WIN32 */ + +typedef struct { + const char *alias; + const char *fontName; +} FamilyMap; + +static FamilyMap familyMap[] = +{ + { "Arial", "Helvetica" }, + { "AvantGarde", "AvantGarde" }, + { "Bookman", "Bookman" }, + { "Courier New", "Courier" }, + { "Courier", "Courier" }, + { "Geneva", "Helvetica" }, + { "Helvetica", "Helvetica" }, + { "Mathematica1", "Helvetica" }, + { "Monaco", "Courier" }, + { "New Century Schoolbook", "NewCenturySchlbk" }, + { "New York", "Times" }, + { "Nimbus Roman No9 L" "Times" }, + { "Nimbus Sans L Condensed","Helvetica" }, + { "Nimbus Sans L", "Helvetica" }, + { "Palatino", "Palatino" }, + { "Standard Symbols L", "Symbol" }, + { "Swiss 721", "Helvetica" }, + { "Symbol", "Symbol" }, + { "Times New Roman", "Times" }, + { "Times Roman", "Times" }, + { "Times", "Times" }, + { "ZapfChancery", "ZapfChancery" }, + { "ZapfDingbats", "ZapfDingbats" } +}; + +static int nFamilyNames = (sizeof(familyMap) / sizeof(FamilyMap)); + +static const char * +FamilyToPsFamily(const char *family) +{ + FamilyMap *fp, *fend; + + if (strncasecmp(family, "itc ", 4) == 0) { + family += 4; + } + for (fp = familyMap, fend = fp + nFamilyNames; fp < fend; fp++) { + if (strcasecmp(fp->alias, family) == 0) { + return fp->fontName; + } + } + return NULL; +} + +/* + *--------------------------------------------------------------------------- + * Routines to convert X drawing functions to PostScript commands. + *--------------------------------------------------------------------------- + */ +void +Blt_Ps_SetClearBackground(Blt_Ps ps) +{ + Blt_Ps_Append(ps, "1 1 1 setrgbcolor\n"); +} + +void +Blt_Ps_XSetCapStyle(Blt_Ps ps, int capStyle) +{ + /* + * X11:not last = 0, butt = 1, round = 2, projecting = 3 + * PS: butt = 0, round = 1, projecting = 2 + */ + if (capStyle > 0) { + capStyle--; + } + Blt_Ps_Format(ps, "%d setlinecap\n", capStyle); +} + +void +Blt_Ps_XSetJoinStyle(Blt_Ps ps, int joinStyle) +{ + /* + * miter = 0, round = 1, bevel = 2 + */ + Blt_Ps_Format(ps, "%d setlinejoin\n", joinStyle); +} + +void +Blt_Ps_XSetLineWidth(Blt_Ps ps, int lineWidth) +{ + if (lineWidth < 1) { + lineWidth = 1; + } + Blt_Ps_Format(ps, "%d setlinewidth\n", lineWidth); +} + +void +Blt_Ps_XSetDashes(Blt_Ps ps, Blt_Dashes *dashesPtr) +{ + + Blt_Ps_Append(ps, "[ "); + if (dashesPtr != NULL) { + unsigned char *vp; + + for (vp = dashesPtr->values; *vp != 0; vp++) { + Blt_Ps_Format(ps, " %d", *vp); + } + } + Blt_Ps_Append(ps, "] 0 setdash\n"); +} + +void +Blt_Ps_XSetLineAttributes( + Blt_Ps ps, + XColor *colorPtr, + int lineWidth, + Blt_Dashes *dashesPtr, + int capStyle, + int joinStyle) +{ + Blt_Ps_XSetJoinStyle(ps, joinStyle); + Blt_Ps_XSetCapStyle(ps, capStyle); + Blt_Ps_XSetForeground(ps, colorPtr); + Blt_Ps_XSetLineWidth(ps, lineWidth); + Blt_Ps_XSetDashes(ps, dashesPtr); + Blt_Ps_Append(ps, "/DashesProc {} def\n"); +} + +void +Blt_Ps_Rectangle(Blt_Ps ps, int x, int y, int width, int height) +{ + Blt_Ps_Append(ps, "newpath\n"); + Blt_Ps_Format(ps, " %d %d moveto\n", x, y); + Blt_Ps_Format(ps, " %d %d rlineto\n", width, 0); + Blt_Ps_Format(ps, " %d %d rlineto\n", 0, height); + Blt_Ps_Format(ps, " %d %d rlineto\n", -width, 0); + Blt_Ps_Append(ps, "closepath\n"); +} + +void +Blt_Ps_XFillRectangle(Blt_Ps ps, double x, double y, int width, int height) +{ + Blt_Ps_Rectangle(ps, (int)x, (int)y, width, height); + Blt_Ps_Append(ps, "fill\n"); +} + +void +Blt_Ps_PolylineFromXPoints(Blt_Ps ps, XPoint *points, int n) +{ + XPoint *pp, *pend; + + pp = points; + Blt_Ps_Append(ps, "newpath\n"); + Blt_Ps_Format(ps, " %d %d moveto\n", pp->x, pp->y); + pp++; + for (pend = points + n; pp < pend; pp++) { + Blt_Ps_Format(ps, " %d %d lineto\n", pp->x, pp->y); + } +} + +void +Blt_Ps_Polyline(Blt_Ps ps, Point2d *screenPts, int nScreenPts) +{ + Point2d *pp, *pend; + + pp = screenPts; + Blt_Ps_Append(ps, "newpath\n"); + Blt_Ps_Format(ps, " %g %g moveto\n", pp->x, pp->y); + for (pp++, pend = screenPts + nScreenPts; pp < pend; pp++) { + Blt_Ps_Format(ps, " %g %g lineto\n", pp->x, pp->y); + } +} + +void +Blt_Ps_Polygon(Blt_Ps ps, Point2d *screenPts, int nScreenPts) +{ + Point2d *pp, *pend; + + pp = screenPts; + Blt_Ps_Append(ps, "newpath\n"); + Blt_Ps_Format(ps, " %g %g moveto\n", pp->x, pp->y); + for (pp++, pend = screenPts + nScreenPts; pp < pend; pp++) { + Blt_Ps_Format(ps, " %g %g lineto\n", pp->x, pp->y); + } + Blt_Ps_Format(ps, " %g %g lineto\n", screenPts[0].x, screenPts[0].y); + Blt_Ps_Append(ps, "closepath\n"); +} + +void +Blt_Ps_XFillPolygon(Blt_Ps ps, Point2d *screenPts, int nScreenPts) +{ + Blt_Ps_Polygon(ps, screenPts, nScreenPts); + Blt_Ps_Append(ps, "fill\n"); +} + +void +Blt_Ps_XDrawSegments(Blt_Ps ps, XSegment *segments, int nSegments) +{ + XSegment *sp, *send; + + for (sp = segments, send = sp + nSegments; sp < send; sp++) { + Blt_Ps_Format(ps, "%d %d moveto %d %d lineto\n", sp->x1, sp->y1, + sp->x2, sp->y2); + Blt_Ps_Append(ps, "DashesProc stroke\n"); + } +} + + +void +Blt_Ps_XFillRectangles(Blt_Ps ps, XRectangle *rectangles, int nRectangles) +{ + XRectangle *rp, *rend; + + for (rp = rectangles, rend = rp + nRectangles; rp < rend; rp++) { + Blt_Ps_XFillRectangle(ps, (double)rp->x, (double)rp->y, + (int)rp->width, (int)rp->height); + } +} + +#ifndef TK_RELIEF_SOLID +#define TK_RELIEF_SOLID -1 /* Set the an impossible value. */ +#endif /* TK_RELIEF_SOLID */ + +void +Blt_Ps_Draw3DRectangle( + Blt_Ps ps, + Tk_3DBorder border, /* Token for border to draw. */ + double x, double y, /* Coordinates of rectangle */ + int width, int height, /* Region to be drawn. */ + int borderWidth, /* Desired width for border, in pixels. */ + int relief) /* Should be either TK_RELIEF_RAISED or + * TK_RELIEF_SUNKEN; indicates position of + * interior of window relative to exterior. */ +{ + Point2d points[7]; + TkBorder *borderPtr = (TkBorder *) border; + XColor *lightPtr, *darkPtr; + XColor *topPtr, *bottomPtr; + XColor light, dark; + int twiceWidth = (borderWidth * 2); + + if ((width < twiceWidth) || (height < twiceWidth)) { + return; + } + if ((relief == TK_RELIEF_SOLID) || + (borderPtr->lightColor == NULL) || (borderPtr->darkColor == NULL)) { + if (relief == TK_RELIEF_SOLID) { + dark.red = dark.blue = dark.green = 0x00; + light.red = light.blue = light.green = 0x00; + relief = TK_RELIEF_SUNKEN; + } else { + light = *borderPtr->bgColor; + dark.red = dark.blue = dark.green = 0xFF; + } + lightPtr = &light; + darkPtr = &dark; + } else { + lightPtr = borderPtr->lightColor; + darkPtr = borderPtr->darkColor; + } + + + /* Handle grooves and ridges with recursive calls. */ + + if ((relief == TK_RELIEF_GROOVE) || (relief == TK_RELIEF_RIDGE)) { + int halfWidth, insideOffset; + + halfWidth = borderWidth / 2; + insideOffset = borderWidth - halfWidth; + Blt_Ps_Draw3DRectangle(ps, border, (double)x, (double)y, + width, height, halfWidth, + (relief == TK_RELIEF_GROOVE) ? TK_RELIEF_SUNKEN : TK_RELIEF_RAISED); + Blt_Ps_Draw3DRectangle(ps, border, + (double)(x + insideOffset), (double)(y + insideOffset), + width - insideOffset * 2, height - insideOffset * 2, halfWidth, + (relief == TK_RELIEF_GROOVE) ? TK_RELIEF_RAISED : TK_RELIEF_SUNKEN); + return; + } + if (relief == TK_RELIEF_RAISED) { + topPtr = lightPtr; + bottomPtr = darkPtr; + } else if (relief == TK_RELIEF_SUNKEN) { + topPtr = darkPtr; + bottomPtr = lightPtr; + } else { + topPtr = bottomPtr = borderPtr->bgColor; + } + Blt_Ps_XSetBackground(ps, bottomPtr); + Blt_Ps_XFillRectangle(ps, x, y + height - borderWidth, width, borderWidth); + Blt_Ps_XFillRectangle(ps, x + width - borderWidth, y, borderWidth, height); + points[0].x = points[1].x = points[6].x = x; + points[0].y = points[6].y = y + height; + points[1].y = points[2].y = y; + points[2].x = x + width; + points[3].x = x + width - borderWidth; + points[3].y = points[4].y = y + borderWidth; + points[4].x = points[5].x = x + borderWidth; + points[5].y = y + height - borderWidth; + if (relief != TK_RELIEF_FLAT) { + Blt_Ps_XSetBackground(ps, topPtr); + } + Blt_Ps_XFillPolygon(ps, points, 7); +} + +void +Blt_Ps_Fill3DRectangle( + Blt_Ps ps, + Tk_3DBorder border, /* Token for border to draw. */ + double x, double y, /* Coordinates of top-left of border area */ + int width, int height, /* Dimension of border to be drawn. */ + int borderWidth, /* Desired width for border, in pixels. */ + int relief) /* Should be either TK_RELIEF_RAISED or + * TK_RELIEF_SUNKEN; indicates position of + * interior of window relative to exterior. */ +{ + TkBorder *borderPtr = (TkBorder *) border; + + Blt_Ps_XSetBackground(ps, borderPtr->bgColor); + Blt_Ps_XFillRectangle(ps, x, y, width, height); + Blt_Ps_Draw3DRectangle(ps, border, x, y, width, height, borderWidth, + relief); +} + +void +Blt_Ps_XSetStipple(Blt_Ps ps, Display *display, Pixmap bitmap) +{ + int width, height; + + Tk_SizeOfBitmap(display, bitmap, &width, &height); + Blt_Ps_Format(ps, + "gsave\n" + " clip\n" + " %d %d\n", + width, height); + Blt_Ps_XSetBitmapData(ps, display, bitmap, width, height); + Blt_Ps_VarAppend(ps, + " StippleFill\n" + "grestore\n", (char *)NULL); +} + +static void +Base85Encode(Blt_DBuffer dBuffer, Tcl_DString *resultPtr) +{ + char *dp; + int count; + int length, nBytes, oldLength; + int n; + unsigned char *sp, *send; + + oldLength = Tcl_DStringLength(resultPtr); + nBytes = Blt_DBuffer_Length(dBuffer); + + /* + * Compute worst-case length of buffer needed for encoding. + * That is 5 characters per 4 bytes. There are newlines every + * 65 characters. The actual size can be smaller, depending upon + * the number of 0 tuples ('z' bytes). + */ + length = oldLength + nBytes; + length += ((nBytes + 3)/4) * 5; /* 5 characters per 4 bytes. */ + length += (nBytes+64) / 65; /* # of newlines. */ + Tcl_DStringSetLength(resultPtr, length); + + + n = count = 0; + dp = Tcl_DStringValue(resultPtr) + oldLength; + for (sp = Blt_DBuffer_Bytes(dBuffer), send = sp + nBytes; sp < send; + sp += 4) { + unsigned int tuple; + + tuple = 0; +#ifdef WORDS_BIGENDIAN + tuple |= (sp[3] << 24); + tuple |= (sp[2] << 16); + tuple |= (sp[1] << 8); + tuple |= sp[0]; +#else + tuple |= (sp[0] << 24); + tuple |= (sp[1] << 16); + tuple |= (sp[2] << 8); + tuple |= sp[3]; +#endif + if (tuple == 0) { + *dp++ = 'z'; + count++; + n++; + } else { + dp[4] = '!' + (tuple % 85); + tuple /= 85; + dp[3] = '!' + (tuple % 85); + tuple /= 85; + dp[2] = '!' + (tuple % 85); + tuple /= 85; + dp[1] = '!' + (tuple % 85); + tuple /= 85; + dp[0] = '!' + (tuple % 85); + dp += 5; + n += 5; + count += 5; + } + if (count > 64) { + *dp++ = '\n'; + n++; + count = 0; + } + } + + { + unsigned int tuple; + + /* Handle remaining bytes (0-3). */ + nBytes = (nBytes & 0x3); + sp -= nBytes; + tuple = 0; + switch (nBytes) { +#ifdef WORDS_BIGENDIAN + case 3: + tuple |= (sp[2] << 8); + case 2: + tuple |= (sp[1] << 16); + case 1: + tuple |= (sp[0] << 24); +#else + case 3: + tuple |= (sp[2] << 24); + case 2: + tuple |= (sp[1] << 16); + case 1: + tuple |= (sp[0] << 8); +#endif + default: + break; + } + if (nBytes > 0) { + tuple /= 85; + if (nBytes > 2) { + dp[3] = '!' + (tuple % 85); + n++; + } + tuple /= 85; + if (nBytes > 1) { + dp[2] = '!' + (tuple % 85); + n++; + } + tuple /= 85; + dp[1] = '!' + (tuple % 85); + tuple /= 85; + dp[0] = '!' + (tuple % 85); + *dp++ = '\n'; + n += 3; + } + Tcl_DStringSetLength(resultPtr, n); + } +} + +static void +AsciiHexEncode(Blt_DBuffer dBuffer, Tcl_DString *resultPtr) +{ + static char hexDigits[] = "0123456789ABCDEF"; + int length, oldLength; + int nBytes; + + /* + * Compute the length of the buffer needed for the encoding. That is 2 + * characters per byte. There are newlines every 64 characters. + */ + length = oldLength = Tcl_DStringLength(resultPtr); + nBytes = Blt_DBuffer_Length(dBuffer) * 2; /* Two characters per byte */ + length += nBytes; + length += (nBytes+63)/64; /* Number of newlines required. */ + Tcl_DStringSetLength(resultPtr, length); + { + unsigned char *sp, *send; + char *bytes, *dp; + int count, n; + + count = n = 0; + dp = bytes = Tcl_DStringValue(resultPtr) + oldLength; + for (sp = Blt_DBuffer_Bytes(dBuffer), + send = sp + Blt_DBuffer_Length(dBuffer); + sp < send; sp++) { + dp[0] = hexDigits[*sp >> 4]; + dp[1] = hexDigits[*sp & 0x0F]; + dp += 2; + n += 2; + count++; + if ((count & 0x1F) == 0) { + *dp++ = '\n'; + n++; + } + } + *dp++ = '\0'; +#ifdef notdef + Tcl_DStringSetLength(resultPtr, n + oldLength); +#endif + } +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_Ps_DrawPicture -- + * + * Translates a picture into 3 component RGB PostScript output. Uses PS + * Language Level 2 operator "colorimage". + * + * Results: + * The dynamic string will contain the PostScript output. + * + *--------------------------------------------------------------------------- + */ +void +Blt_Ps_DrawPicture(PostScript *psPtr, Blt_Picture picture, double x, double y) +{ + PageSetup *setupPtr = psPtr->setupPtr; + int w, h; + Blt_DBuffer dBuffer; + + w = Blt_PictureWidth(picture); + h = Blt_PictureHeight(picture); + Blt_Ps_Format(psPtr, + "gsave\n" + "/DeviceRGB setcolorspace\n" + "%g %g translate\n" + "%d %d scale\n", x, y, w, h); + if ((setupPtr->flags & PS_GREYSCALE) || (setupPtr->level == 1)) { + int strSize; + + strSize = (setupPtr->flags & PS_GREYSCALE) ? w : w * 3; + Blt_Ps_Format(psPtr, + "/picstr %d string def\n" + "%d %d 8\n" + "[%d 0 0 %d 0 %d]\n" + "{\n" + " currentfile picstr readhexstring pop\n" + "}\n", strSize, w, h, w, -h, h); + if (setupPtr->flags & PS_GREYSCALE) { + Blt_Picture greyscale; + + Blt_Ps_Append(psPtr, "image\n"); + greyscale = Blt_GreyscalePicture(picture); + dBuffer = Blt_PictureToDBuffer(picture, 1); + Blt_FreePicture(greyscale); + } else { + Blt_Ps_Append(psPtr, "false 3 colorimage\n"); + dBuffer = Blt_PictureToDBuffer(picture, 3); + } + AsciiHexEncode(dBuffer, &psPtr->dString); + Blt_DBuffer_Free(dBuffer); + } else { + Blt_Ps_Format(psPtr, + "<<\n" + "/ImageType 1\n" + "/Width %d\n" + "/Height %d\n" + "/BitsPerComponent 8\n" + "/Decode [0 1 0 1 0 1]\n" + "/ImageMatrix [%d 0 0 %d 0 %d]\n" + "/Interpolate true\n" + "/DataSource currentfile /ASCII85Decode filter\n" + ">>\n" + "image\n", w, h, w, -h, h); + dBuffer = Blt_PictureToDBuffer(picture, 3); + Base85Encode(dBuffer, &psPtr->dString); + Blt_DBuffer_Free(dBuffer); + } + Blt_Ps_Append(psPtr, "\ngrestore\n\n"); +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_Ps_XDrawWindow -- + * + * Converts a Tk window to PostScript. If the window could not be + * "snapped", then a grey rectangle is drawn in its place. + * + * Results: + * None. + * + *--------------------------------------------------------------------------- + */ +void +Blt_Ps_XDrawWindow(Blt_Ps ps, Tk_Window tkwin, double x, double y) +{ + Blt_Picture picture; + + picture = Blt_DrawableToPicture(tkwin, Tk_WindowId(tkwin), 0, 0, + Tk_Width(tkwin), Tk_Height(tkwin), GAMMA); + if (picture == NULL) { + /* Can't grab window image so paint the window area grey */ + Blt_Ps_VarAppend(ps, "% Can't grab window \"", Tk_PathName(tkwin), + "\"\n", (char *)NULL); + Blt_Ps_Append(ps, "0.5 0.5 0.5 setrgbcolor\n"); + Blt_Ps_XFillRectangle(ps, x, y, Tk_Width(tkwin), Tk_Height(tkwin)); + return; + } + Blt_Ps_DrawPicture(ps, picture, x, y); + Blt_FreePicture(picture); +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_Ps_DrawPhoto -- + * + * Output a PostScript image string of the given photo image. The photo + * is first converted into a picture and then translated into PostScript. + * + * Results: + * None. + * + * Side Effects: + * The PostScript output representing the photo is appended to the + * psPtr's dynamic string. + * + *--------------------------------------------------------------------------- + */ +void +Blt_Ps_DrawPhoto(Blt_Ps ps, Tk_PhotoHandle photo, double x, double y) +{ + Blt_Picture picture; + + picture = Blt_PhotoToPicture(photo); + Blt_Ps_DrawPicture(ps, picture, x, y); + Blt_FreePicture(picture); +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_Ps_XSetFont -- + * + * Map the Tk font to a PostScript font and point size. + * + * If a TCL array variable was specified, each element should be indexed + * by the X11 font name and contain a list of 1-2 elements; the + * PostScript font name and the desired point size. The point size may + * be omitted and the X font point size will be used. + * + * Otherwise, if the foundry is "Adobe", we try to do a plausible mapping + * looking at the full name of the font and building a string in the form + * of "Family-TypeFace". + * + * Returns: + * None. + * + * Side Effects: + * PostScript commands are output to change the type and the point size + * of the current font. + * + *--------------------------------------------------------------------------- + */ + +void +Blt_Ps_XSetFont(PostScript *psPtr, Blt_Font font) +{ + Tcl_Interp *interp = psPtr->interp; + const char *family; + + /* Use the font variable information if it exists. */ + if ((psPtr->setupPtr != NULL) && (psPtr->setupPtr->fontVarName != NULL)) { + char *value; + + value = (char *)Tcl_GetVar2(interp, psPtr->setupPtr->fontVarName, + Blt_NameOfFont(font), 0); + if (value != NULL) { + const char **argv = NULL; + int argc; + int newSize; + double pointSize; + const char *fontName; + + if (Tcl_SplitList(NULL, value, &argc, &argv) != TCL_OK) { + return; + } + fontName = argv[0]; + if ((argc != 2) || + (Tcl_GetInt(interp, argv[1], &newSize) != TCL_OK)) { + Blt_Free(argv); + return; + } + pointSize = (double)newSize; + Blt_Ps_Format(psPtr, "%g /%s SetFont\n", pointSize, + fontName); + Blt_Free(argv); + return; + } + /*FallThru*/ + } + + /* + * Check to see if it's a PostScript font. Tk_PostScriptFontName silently + * generates a bogus PostScript font name, so we have to check to see if + * this is really a PostScript font first before we call it. + */ + family = FamilyToPsFamily(Blt_FamilyOfFont(font)); + if (family != NULL) { + Tcl_DString dString; + double pointSize; + + Tcl_DStringInit(&dString); + pointSize = (double)Blt_PostscriptFontName(font, &dString); + Blt_Ps_Format(psPtr, "%g /%s SetFont\n", pointSize, + Tcl_DStringValue(&dString)); + Tcl_DStringFree(&dString); + return; + } + Blt_Ps_Append(psPtr, "12.0 /Helvetica-Bold SetFont\n"); +} + +static void +TextLayoutToPostScript(Blt_Ps ps, int x, int y, TextLayout *textPtr) +{ + char *bp, *dst; + int count; /* Counts the # of bytes written to the + * intermediate scratch buffer. */ + const char *src, *end; + TextFragment *fragPtr; + int i; + unsigned char c; +#if HAVE_UTF + Tcl_UniChar ch; +#endif + int limit; + + limit = POSTSCRIPT_BUFSIZ - 4; /* High water mark for scratch buffer. */ + fragPtr = textPtr->fragments; + for (i = 0; i < textPtr->nFrags; i++, fragPtr++) { + if (fragPtr->count < 1) { + continue; + } + Blt_Ps_Append(ps, "("); + count = 0; + dst = Blt_Ps_GetScratchBuffer(ps); + src = fragPtr->text; + end = fragPtr->text + fragPtr->count; + while (src < end) { + if (count > limit) { + /* Don't let the scatch buffer overflow */ + dst = Blt_Ps_GetScratchBuffer(ps); + dst[count] = '\0'; + Blt_Ps_Append(ps, dst); + count = 0; + } +#if HAVE_UTF + /* + * INTL: For now we just treat the characters as binary data and + * display the lower byte. Eventually this should be revised to + * handle international postscript fonts. + */ + src += Tcl_UtfToUniChar(src, &ch); + c = (unsigned char)(ch & 0xff); +#else + c = *src++; +#endif + + if ((c == '\\') || (c == '(') || (c == ')')) { + /* + * If special PostScript characters characters "\", "(", and + * ")" are contained in the text string, prepend backslashes + * to them. + */ + *dst++ = '\\'; + *dst++ = c; + count += 2; + } else if ((c < ' ') || (c > '~')) { + /* Convert non-printable characters into octal. */ + sprintf_s(dst, 5, "\\%03o", c); + dst += 4; + count += 4; + } else { + *dst++ = c; + count++; + } + } + bp = Blt_Ps_GetScratchBuffer(ps); + bp[count] = '\0'; + Blt_Ps_Append(ps, bp); + Blt_Ps_Format(ps, ") %d %d %d DrawAdjText\n", + fragPtr->width, x + fragPtr->x, y + fragPtr->y); + } +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_Ps_DrawText -- + * + * Output PostScript commands to print a text string. The string may be + * rotated at any arbitrary angle, and placed according the anchor type + * given. The anchor indicates how to interpret the window coordinates as + * an anchor for the text bounding box. + * + * Results: + * None. + * + * Side Effects: + * Text string is drawn using the given font and GC on the graph window + * at the given coordinates, anchor, and rotation + * + *--------------------------------------------------------------------------- + */ +void +Blt_Ps_DrawText( + Blt_Ps ps, + const char *string, /* String to convert to PostScript */ + TextStyle *tsPtr, /* Text attribute information */ + double x, double y) /* Window coordinates where to print text */ +{ + TextLayout *textPtr; + Point2d t; + + if ((string == NULL) || (*string == '\0')) { /* Empty string, do nothing */ + return; + } + textPtr = Blt_Ts_CreateLayout(string, -1, tsPtr); + { + float angle; + double rw, rh; + + angle = FMOD(tsPtr->angle, (double)360.0); + Blt_GetBoundingBox(textPtr->width, textPtr->height, angle, &rw, &rh, + (Point2d *)NULL); + /* + * Find the center of the bounding box + */ + t = Blt_AnchorPoint(x, y, rw, rh, tsPtr->anchor); + t.x += rw * 0.5; + t.y += rh * 0.5; + } + + /* Initialize text (sets translation and rotation) */ + Blt_Ps_Format(ps, "%d %d %g %g %g BeginText\n", textPtr->width, + textPtr->height, tsPtr->angle, t.x, t.y); + + Blt_Ps_XSetFont(ps, tsPtr->font); + + Blt_Ps_XSetForeground(ps, tsPtr->color); + TextLayoutToPostScript(ps, 0, 0, textPtr); + Blt_Free(textPtr); + Blt_Ps_Append(ps, "EndText\n"); +} + +void +Blt_Ps_XDrawLines(Blt_Ps ps, XPoint *points, int n) +{ + int nLeft; + + if (n <= 0) { + return; + } + for (nLeft = n; nLeft > 0; nLeft -= PS_MAXPATH) { + int length; + + length = MIN(PS_MAXPATH, nLeft); + Blt_Ps_PolylineFromXPoints(ps, points, length); + Blt_Ps_Append(ps, "DashesProc stroke\n"); + points += length; + } +} + +void +Blt_Ps_DrawPolyline(Blt_Ps ps, Point2d *points, int nPoints) +{ + int nLeft; + + if (nPoints <= 0) { + return; + } + for (nLeft = nPoints; nLeft > 0; nLeft -= PS_MAXPATH) { + int length; + + length = MIN(PS_MAXPATH, nLeft); + Blt_Ps_Polyline(ps, points, length); + Blt_Ps_Append(ps, "DashesProc stroke\n"); + points += length; + } +} + +void +Blt_Ps_DrawBitmap( + Blt_Ps ps, + Display *display, + Pixmap bitmap, /* Bitmap to be converted to PostScript */ + double xScale, double yScale) +{ + int width, height; + double sw, sh; + + Tk_SizeOfBitmap(display, bitmap, &width, &height); + sw = (double)width * xScale; + sh = (double)height * yScale; + Blt_Ps_Append(ps, " gsave\n"); + Blt_Ps_Format(ps, " %g %g translate\n", sw * -0.5, sh * 0.5); + Blt_Ps_Format(ps, " %g %g scale\n", sw, -sh); + Blt_Ps_Format(ps, " %d %d true [%d 0 0 %d 0 %d] {", + width, height, width, -height, height); + Blt_Ps_XSetBitmapData(ps, display, bitmap, width, height); + Blt_Ps_Append(ps, " } imagemask\n grestore\n"); +} + +void +Blt_Ps_Draw2DSegments(Blt_Ps ps, Segment2d *segments, int nSegments) +{ + Segment2d *sp, *send; + + Blt_Ps_Append(ps, "newpath\n"); + for (sp = segments, send = sp + nSegments; sp < send; sp++) { + Blt_Ps_Format(ps, " %g %g moveto %g %g lineto\n", + sp->p.x, sp->p.y, sp->q.x, sp->q.y); + Blt_Ps_Append(ps, "DashesProc stroke\n"); + } +} + +void +Blt_Ps_FontName(const char *family, int flags, Tcl_DString *resultPtr) +{ + const char *familyName, *weightName, *slantName; + int len; + + len = Tcl_DStringLength(resultPtr); + + familyName = FamilyToPsFamily(family); + if (familyName == NULL) { + Tcl_UniChar ch; + char *src, *dest; + int upper; + + /* + * Inline, capitalize the first letter of each word, lowercase the + * rest of the letters in each word, and then take out the spaces + * between the words. This may make the DString shorter, which is + * safe to do. + */ + Tcl_DStringAppend(resultPtr, family, -1); + src = dest = Tcl_DStringValue(resultPtr) + len; + upper = TRUE; + while (*src != '\0') { + while (isspace(*src)) { /* INTL: ISO space */ + src++; + upper = TRUE; + } + src += Tcl_UtfToUniChar(src, &ch); + if (upper) { + ch = Tcl_UniCharToUpper(ch); + upper = FALSE; + } else { + ch = Tcl_UniCharToLower(ch); + } + dest += Tcl_UniCharToUtf(ch, dest); + } + *dest = '\0'; + Tcl_DStringSetLength(resultPtr, dest - Tcl_DStringValue(resultPtr)); + familyName = Tcl_DStringValue(resultPtr) + len; + } + if (familyName != Tcl_DStringValue(resultPtr) + len) { + Tcl_DStringAppend(resultPtr, familyName, -1); + familyName = Tcl_DStringValue(resultPtr) + len; + } + if (strcasecmp(familyName, "NewCenturySchoolbook") == 0) { + Tcl_DStringSetLength(resultPtr, len); + Tcl_DStringAppend(resultPtr, "NewCenturySchlbk", -1); + familyName = Tcl_DStringValue(resultPtr) + len; + } + + /* Get the string to use for the weight. */ + weightName = NULL; + if (flags & FONT_BOLD) { + if ((strcmp(familyName, "Bookman") == 0) || + (strcmp(familyName, "AvantGarde") == 0)) { + weightName = "Demi"; + } else { + weightName = "Bold"; + } + } else { + if (strcmp(familyName, "Bookman") == 0) { + weightName = "Light"; + } else if (strcmp(familyName, "AvantGarde") == 0) { + weightName = "Book"; + } else if (strcmp(familyName, "ZapfChancery") == 0) { + weightName = "Medium"; + } + } + + /* Get the string to use for the slant. */ + slantName = NULL; + if (flags & FONT_ITALIC) { + if ((strcmp(familyName, "Helvetica") == 0) || + (strcmp(familyName, "Courier") == 0) || + (strcmp(familyName, "AvantGarde") == 0)) { + slantName = "Oblique"; + } else { + slantName = "Italic"; + } + } + + /* + * The string "Roman" needs to be added to some fonts that are not bold + * and not italic. + */ + if ((slantName == NULL) && (weightName == NULL)) { + if ((strcmp(familyName, "Times") == 0) || + (strcmp(familyName, "NewCenturySchlbk") == 0) || + (strcmp(familyName, "Palatino") == 0)) { + Tcl_DStringAppend(resultPtr, "-Roman", -1); + } + } else { + Tcl_DStringAppend(resultPtr, "-", -1); + if (weightName != NULL) { + Tcl_DStringAppend(resultPtr, weightName, -1); + } + if (slantName != NULL) { + Tcl_DStringAppend(resultPtr, slantName, -1); + } + } +} diff --git a/src/bltGrPSOutput.h b/src/bltGrPSOutput.h new file mode 100644 index 0000000..944faf8 --- /dev/null +++ b/src/bltGrPSOutput.h @@ -0,0 +1,215 @@ + +/* + * bltPs.h -- + * + * Copyright 1993-2004 George A Howlett. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef _BLT_PS_H +#define _BLT_PS_H + +#include "bltPicture.h" + +/* + * PageSetup -- + * + * Structure contains information specific to the layout of the page for + * printing the graph. + * + */ +typedef struct { + /* User configurable fields */ + + int reqWidth, reqHeight; /* If greater than zero, represents the + * requested dimensions of the printed graph */ + int reqPaperWidth; + int reqPaperHeight; /* Requested dimensions for the PostScript + * page. Can constrain the size of the graph + * if the graph (plus padding) is larger than + * the size of the page. */ + Blt_Pad xPad, yPad; /* Requested padding on the exterior of the + * graph. This forms the bounding box for + * the page. */ + const char *colorVarName; /* If non-NULL, is the name of a TCL array + * variable containing X to output device color + * translations */ + const char *fontVarName; /* If non-NULL, is the name of a TCL array + * variable containing X to output device font + * translations */ + int level; /* PostScript Language level 1-3 */ + unsigned int flags; + + const char **comments; /* User supplied comments to be added. */ + + /* Computed fields */ + + short int left, bottom; /* Bounding box of the plot in the page. */ + short int right, top; + + float scale; /* Scale of page. Set if "-maxpect" option + * is set, otherwise 1.0. */ + + int paperHeight; + int paperWidth; + +} PageSetup; + +#define PS_GREYSCALE (1<<0) +#define PS_LANDSCAPE (1<<2) +#define PS_CENTER (1<<3) +#define PS_MAXPECT (1<<4) +#define PS_DECORATIONS (1<<5) +#define PS_FOOTER (1<<6) +#define PS_FMT_NONE 0 +#define PS_FMT_MASK (PS_FMT_WMF|PS_FMT_EPSI|PS_FMT_TIFF) +#define PS_FMT_WMF (1<<8) +#define PS_FMT_EPSI (1<<9) +#define PS_FMT_TIFF (1<<10) + +typedef struct _Blt_Ps *Blt_Ps; + +BLT_EXTERN Blt_Ps Blt_Ps_Create(Tcl_Interp *interp, PageSetup *setupPtr); + +BLT_EXTERN void Blt_Ps_Free(Blt_Ps ps); + +BLT_EXTERN const char *Blt_Ps_GetValue(Blt_Ps ps, int *lengthPtr); + +BLT_EXTERN Tcl_Interp *Blt_Ps_GetInterp(Blt_Ps ps); + +BLT_EXTERN Tcl_DString *Blt_Ps_GetDString(Blt_Ps ps); + +BLT_EXTERN char *Blt_Ps_GetScratchBuffer(Blt_Ps ps); + +BLT_EXTERN void Blt_Ps_SetInterp(Blt_Ps ps, Tcl_Interp *interp); + +BLT_EXTERN void Blt_Ps_Append(Blt_Ps ps, const char *string); + +BLT_EXTERN void Blt_Ps_AppendBytes(Blt_Ps ps, const char *string, int nBytes); + +BLT_EXTERN void Blt_Ps_VarAppend TCL_VARARGS(Blt_Ps, ps); + +BLT_EXTERN void Blt_Ps_Format TCL_VARARGS(Blt_Ps, ps); + +BLT_EXTERN void Blt_Ps_SetClearBackground(Blt_Ps ps); + +BLT_EXTERN int Blt_Ps_IncludeFile(Tcl_Interp *interp, Blt_Ps ps, + const char *fileName); + +BLT_EXTERN int Blt_Ps_GetPicaFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, + int *picaPtr); + +BLT_EXTERN int Blt_Ps_GetPadFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, + Blt_Pad *padPtr); + +BLT_EXTERN int Blt_Ps_ComputeBoundingBox(PageSetup *setupPtr, int w, int h); + +BLT_EXTERN void Blt_Ps_DrawPicture(Blt_Ps ps, Blt_Picture picture, + double x, double y); + +BLT_EXTERN void Blt_Ps_Rectangle(Blt_Ps ps, int x, int y, int w, int h); + + +BLT_EXTERN int Blt_Ps_SaveFile(Tcl_Interp *interp, Blt_Ps ps, + const char *fileName); + +#ifdef _TK + +#include "bltFont.h" +#include "bltText.h" + +BLT_EXTERN void Blt_Ps_XSetLineWidth(Blt_Ps ps, int lineWidth); + +BLT_EXTERN void Blt_Ps_XSetBackground(Blt_Ps ps, XColor *colorPtr); + +BLT_EXTERN void Blt_Ps_XSetBitmapData(Blt_Ps ps, Display *display, + Pixmap bitmap, int width, int height); + +BLT_EXTERN void Blt_Ps_XSetForeground(Blt_Ps ps, XColor *colorPtr); + +BLT_EXTERN void Blt_Ps_XSetFont(Blt_Ps ps, Blt_Font font); + +BLT_EXTERN void Blt_Ps_XSetDashes(Blt_Ps ps, Blt_Dashes *dashesPtr); + +BLT_EXTERN void Blt_Ps_XSetLineAttributes(Blt_Ps ps, XColor *colorPtr, + int lineWidth, Blt_Dashes *dashesPtr, int capStyle, int joinStyle); + +BLT_EXTERN void Blt_Ps_XSetStipple(Blt_Ps ps, Display *display, Pixmap bitmap); + +BLT_EXTERN void Blt_Ps_Polyline(Blt_Ps ps, Point2d *screenPts, int nScreenPts); + +BLT_EXTERN void Blt_Ps_XDrawLines(Blt_Ps ps, XPoint *points, int n); + +BLT_EXTERN void Blt_Ps_XDrawSegments(Blt_Ps ps, XSegment *segments, + int nSegments); + +BLT_EXTERN void Blt_Ps_DrawPolyline(Blt_Ps ps, Point2d *points, int n); + +BLT_EXTERN void Blt_Ps_Draw2DSegments(Blt_Ps ps, Segment2d *segments, + int nSegments); + +BLT_EXTERN void Blt_Ps_Draw3DRectangle(Blt_Ps ps, Tk_3DBorder border, + double x, double y, int width, int height, int borderWidth, int relief); + +BLT_EXTERN void Blt_Ps_Fill3DRectangle(Blt_Ps ps, Tk_3DBorder border, double x, + double y, int width, int height, int borderWidth, int relief); + +BLT_EXTERN void Blt_Ps_XFillRectangle(Blt_Ps ps, double x, double y, + int width, int height); + +BLT_EXTERN void Blt_Ps_XFillRectangles(Blt_Ps ps, XRectangle *rects, int n); + +BLT_EXTERN void Blt_Ps_XFillPolygon(Blt_Ps ps, Point2d *screenPts, + int nScreenPts); + +BLT_EXTERN void Blt_Ps_DrawPhoto(Blt_Ps ps, Tk_PhotoHandle photoToken, + double x, double y); + +BLT_EXTERN void Blt_Ps_XDrawWindow(Blt_Ps ps, Tk_Window tkwin, + double x, double y); + +BLT_EXTERN void Blt_Ps_DrawText(Blt_Ps ps, const char *string, + TextStyle *attrPtr, double x, double y); + +BLT_EXTERN void Blt_Ps_DrawBitmap(Blt_Ps ps, Display *display, Pixmap bitmap, + double scaleX, double scaleY); + +BLT_EXTERN void Blt_Ps_XSetCapStyle(Blt_Ps ps, int capStyle); + +BLT_EXTERN void Blt_Ps_XSetJoinStyle(Blt_Ps ps, int joinStyle); + +BLT_EXTERN void Blt_Ps_PolylineFromXPoints(Blt_Ps ps, XPoint *points, int n); + +BLT_EXTERN void Blt_Ps_Polygon(Blt_Ps ps, Point2d *screenPts, int nScreenPts); + +BLT_EXTERN void Blt_Ps_SetPrinting(Blt_Ps ps, int value); +BLT_EXTERN int Blt_Ps_IsPrinting(void); + +BLT_EXTERN int Blt_Ps_TextWidth(Blt_Font font, const char *string, int nBytes); + +BLT_EXTERN int Blt_Ps_GetFontMetrics(Blt_Font font, Blt_FontMetrics *fmPtr); + +BLT_EXTERN void Blt_Ps_FontName(const char *family, int flags, + Tcl_DString *resultPtr); + +#endif /* _TK */ + +#endif /* BLT_PS_H */ diff --git a/src/bltGrPenOp.C b/src/bltGrPenOp.C new file mode 100644 index 0000000..2e13b3a --- /dev/null +++ b/src/bltGrPenOp.C @@ -0,0 +1,765 @@ + +/* + * bltGrPen.c -- + * + * This module implements pens for the BLT graph widget. + * + * Copyright 1996-2004 George A Howlett. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include "bltGraph.h" +#include "bltOp.h" +#include <X11/Xutil.h> + +typedef int (GraphPenProc)(Tcl_Interp *interp, Graph *graphPtr, int objc, + Tcl_Obj *const *objv); + +static Blt_OptionFreeProc FreeColor; +static Blt_OptionParseProc ObjToColor; +static Blt_OptionPrintProc ColorToObj; +Blt_CustomOption bltColorOption = { + ObjToColor, ColorToObj, FreeColor, (ClientData)0 +}; + +static Blt_OptionFreeProc FreePen; +static Blt_OptionParseProc ObjToPen; +static Blt_OptionPrintProc PenToObj; +Blt_CustomOption bltBarPenOption = { + ObjToPen, PenToObj, FreePen, (ClientData)CID_ELEM_BAR +}; +Blt_CustomOption bltLinePenOption = { + ObjToPen, PenToObj, FreePen, (ClientData)CID_ELEM_LINE +}; + +/*ARGSUSED*/ +static void +FreeColor( + ClientData clientData, /* Not used. */ + Display *display, /* Not used. */ + char *widgRec, + int offset) +{ + XColor *colorPtr = *(XColor **)(widgRec + offset); + + if ((colorPtr != NULL) && (colorPtr != COLOR_DEFAULT)) { + Tk_FreeColor(colorPtr); + } +} + +/* + *--------------------------------------------------------------------------- + + * ObjToColor -- + * + * Convert the string representation of a color into a XColor pointer. + * + * Results: + * The return value is a standard TCL result. The color pointer is + * written into the widget record. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +ObjToColor( + ClientData clientData, /* Not used. */ + Tcl_Interp *interp, /* Interpreter to send results back to */ + Tk_Window tkwin, /* Not used. */ + Tcl_Obj *objPtr, /* String representing color */ + char *widgRec, /* Widget record */ + int offset, /* Offset to field in structure */ + int flags) +{ + XColor **colorPtrPtr = (XColor **)(widgRec + offset); + XColor *colorPtr; + const char *string; + char c; + int length; + + string = Tcl_GetStringFromObj(objPtr, &length); + c = string[0]; + + if ((c == '\0') && (flags & BLT_CONFIG_NULL_OK)) { + if ((*colorPtrPtr != NULL) && (*colorPtrPtr != COLOR_DEFAULT)) { + Tk_FreeColor(*colorPtrPtr); + } + *colorPtrPtr = NULL; + return TCL_OK; + } + if ((c == 'd') && (strncmp(string, "defcolor", length) == 0)) { + if ((*colorPtrPtr != NULL) && (*colorPtrPtr != COLOR_DEFAULT)) { + Tk_FreeColor(*colorPtrPtr); + } + *colorPtrPtr = COLOR_DEFAULT; + return TCL_OK; + } + colorPtr = Tk_AllocColorFromObj(interp, tkwin, objPtr); + if (colorPtr == NULL) { + return TCL_ERROR; + } + if ((*colorPtrPtr != NULL) && (*colorPtrPtr != COLOR_DEFAULT)) { + Tk_FreeColor(*colorPtrPtr); + } + *colorPtrPtr = colorPtr; + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * ColorToObj -- + * + * Convert the color value into a string. + * + * Results: + * The string representing the symbol color is returned. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static Tcl_Obj * +ColorToObj( + ClientData clientData, /* Not used. */ + Tcl_Interp *interp, /* Not used. */ + Tk_Window tkwin, /* Not used. */ + char *widgRec, /* Widget information record */ + int offset, /* Offset to field in structure */ + int flags) /* Not used. */ +{ + XColor *colorPtr = *(XColor **)(widgRec + offset); + Tcl_Obj *objPtr; + + if (colorPtr == NULL) { + objPtr = Tcl_NewStringObj("", -1); + } else if (colorPtr == COLOR_DEFAULT) { + objPtr = Tcl_NewStringObj("defcolor", -1); + } else { + objPtr = Tcl_NewStringObj(Tk_NameOfColor(colorPtr), -1); + } + return objPtr; +} + +/*ARGSUSED*/ +static void +FreePen( + ClientData clientData, /* Not used. */ + Display *display, /* Not used. */ + char *widgRec, + int offset) +{ + Pen **penPtrPtr = (Pen **)(widgRec + offset); + + if (*penPtrPtr != NULL) { + Blt_FreePen(*penPtrPtr); + } +} + +/* + *--------------------------------------------------------------------------- + * + * ObjToPen -- + * + * Convert the color value into a string. + * + * Results: + * The string representing the symbol color is returned. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +ObjToPen( + ClientData clientData, /* Not used. */ + Tcl_Interp *interp, /* Interpreter to send results back to */ + Tk_Window tkwin, /* Not used. */ + Tcl_Obj *objPtr, /* String representing pen */ + char *widgRec, /* Widget record */ + int offset, /* Offset to field in structure */ + int flags) +{ + Pen **penPtrPtr = (Pen **)(widgRec + offset); + const char *string; + + string = Tcl_GetString(objPtr); + if ((string[0] == '\0') && (flags & BLT_CONFIG_NULL_OK)) { + Blt_FreePen(*penPtrPtr); + *penPtrPtr = NULL; + } else { + Pen *penPtr; + Graph *graphPtr; + ClassId classId = (ClassId)clientData; /* Element type. */ + + graphPtr = Blt_GetGraphFromWindowData(tkwin); + assert(graphPtr); + + if (classId == CID_NONE) { + classId = graphPtr->classId; + } + if (Blt_GetPenFromObj(interp, graphPtr, objPtr, classId, &penPtr) + != TCL_OK) { + return TCL_ERROR; + } + Blt_FreePen(*penPtrPtr); + *penPtrPtr = penPtr; + } + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * PenToObj -- + * + * Parse the name of the name. + * + * Results: + * The return value is a standard TCL result. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static Tcl_Obj * +PenToObj( + ClientData clientData, /* Not used. */ + Tcl_Interp *interp, /* Not used. */ + Tk_Window tkwin, /* Not used. */ + char *widgRec, /* Widget information record */ + int offset, /* Offset to field in structure */ + int flags) /* Not used. */ +{ + Pen *penPtr = *(Pen **)(widgRec + offset); + + if (penPtr == NULL) { + return Tcl_NewStringObj("", -1); + } else { + return Tcl_NewStringObj(penPtr->name, -1); + } +} + +/* + *--------------------------------------------------------------------------- + * + * GetPenFromObj -- + * + * Find and return the pen style from a given name. + * + * Results: + * A standard TCL result. + * + *--------------------------------------------------------------------------- + */ +static int +GetPenFromObj(Tcl_Interp *interp, Graph *graphPtr, Tcl_Obj *objPtr, + Pen **penPtrPtr) +{ + Blt_HashEntry *hPtr; + Pen *penPtr; + const char *name; + + penPtr = NULL; + name = Tcl_GetString(objPtr); + hPtr = Blt_FindHashEntry(&graphPtr->penTable, name); + if (hPtr != NULL) { + penPtr = Blt_GetHashValue(hPtr); + if (penPtr->flags & DELETE_PENDING) { + penPtr = NULL; + } + } + if (penPtr == NULL) { + if (interp != NULL) { + Tcl_AppendResult(interp, "can't find pen \"", name, "\" in \"", + Tk_PathName(graphPtr->tkwin), "\"", (char *)NULL); + } + return TCL_ERROR; + } + *penPtrPtr = penPtr; + return TCL_OK; +} + +static void +DestroyPen(Pen *penPtr) +{ + Graph *graphPtr = penPtr->graphPtr; + + Blt_FreeOptions(penPtr->configSpecs, (char *)penPtr, graphPtr->display, 0); + (*penPtr->destroyProc) (graphPtr, penPtr); + if ((penPtr->name != NULL) && (penPtr->name[0] != '\0')) { + Blt_Free(penPtr->name); + } + if (penPtr->hashPtr != NULL) { + Blt_DeleteHashEntry(&graphPtr->penTable, penPtr->hashPtr); + } + Blt_Free(penPtr); +} + +void +Blt_FreePen(Pen *penPtr) +{ + if (penPtr != NULL) { + penPtr->refCount--; + if ((penPtr->refCount == 0) && (penPtr->flags & DELETE_PENDING)) { + DestroyPen(penPtr); + } + } +} + +Pen * +Blt_CreatePen(Graph *graphPtr, const char *penName, ClassId classId, + int objc, Tcl_Obj *const *objv) +{ + Pen *penPtr; + Blt_HashEntry *hPtr; + unsigned int configFlags; + int isNew; + int i; + + /* + * Scan the option list for a "-type" entry. This will indicate what type + * of pen we are creating. Otherwise we'll default to the suggested type. + * Last -type option wins. + */ + for (i = 0; i < objc; i += 2) { + char *string; + int length; + + string = Tcl_GetStringFromObj(objv[i], &length); + if ((length > 2) && (strncmp(string, "-type", length) == 0)) { + char *arg; + + arg = Tcl_GetString(objv[i + 1]); + if (strcmp(arg, "bar") == 0) { + classId = CID_ELEM_BAR; + } else if (strcmp(arg, "line") == 0) { + classId = CID_ELEM_LINE; + } else if (strcmp(arg, "strip") == 0) { + classId = CID_ELEM_LINE; + } else if (strcmp(arg, "contour") == 0) { + classId = CID_ELEM_CONTOUR; + } else { + Tcl_AppendResult(graphPtr->interp, "unknown pen type \"", + arg, "\" specified", (char *)NULL); + return NULL; + } + } + } + if (classId == CID_ELEM_STRIP) { + classId = CID_ELEM_LINE; + } + hPtr = Blt_CreateHashEntry(&graphPtr->penTable, penName, &isNew); + if (!isNew) { + penPtr = Blt_GetHashValue(hPtr); + if ((penPtr->flags & DELETE_PENDING) == 0) { + Tcl_AppendResult(graphPtr->interp, "pen \"", penName, + "\" already exists in \"", Tk_PathName(graphPtr->tkwin), "\"", + (char *)NULL); + return NULL; + } + if (penPtr->classId != classId) { + Tcl_AppendResult(graphPtr->interp, "pen \"", penName, + "\" in-use: can't change pen type from \"", + Blt_GraphClassName(penPtr->classId), "\" to \"", + Blt_GraphClassName(classId), "\"", (char *)NULL); + return NULL; + } + penPtr->flags &= ~DELETE_PENDING; /* Undelete the pen. */ + } else { + if (classId == CID_ELEM_BAR) { + penPtr = Blt_BarPen(penName); + } else { + penPtr = Blt_LinePen(penName); + } + penPtr->classId = classId; + penPtr->hashPtr = hPtr; + penPtr->graphPtr = graphPtr; + Blt_SetHashValue(hPtr, penPtr); + } + configFlags = (penPtr->flags & (ACTIVE_PEN | NORMAL_PEN)); + if (Blt_ConfigureComponentFromObj(graphPtr->interp, graphPtr->tkwin, + penPtr->name, "Pen", penPtr->configSpecs, objc, objv, + (char *)penPtr, configFlags) != TCL_OK) { + if (isNew) { + DestroyPen(penPtr); + } + return NULL; + } + (*penPtr->configProc) (graphPtr, penPtr); + return penPtr; +} + +int +Blt_GetPenFromObj(Tcl_Interp *interp, Graph *graphPtr, Tcl_Obj *objPtr, + ClassId classId, Pen **penPtrPtr) +{ + Blt_HashEntry *hPtr; + Pen *penPtr; + const char *name; + + penPtr = NULL; + name = Tcl_GetString(objPtr); + hPtr = Blt_FindHashEntry(&graphPtr->penTable, name); + if (hPtr != NULL) { + penPtr = Blt_GetHashValue(hPtr); + if (penPtr->flags & DELETE_PENDING) { + penPtr = NULL; + } + } + if (penPtr == NULL) { + if (interp != NULL) { + Tcl_AppendResult(interp, "can't find pen \"", name, "\" in \"", + Tk_PathName(graphPtr->tkwin), "\"", (char *)NULL); + } + return TCL_ERROR; + } + if (classId == CID_ELEM_STRIP) { + classId = CID_ELEM_LINE; + } + if (penPtr->classId != classId) { + if (interp != NULL) { + Tcl_AppendResult(interp, "pen \"", name, + "\" is the wrong type (is \"", + Blt_GraphClassName(penPtr->classId), "\"", ", wanted \"", + Blt_GraphClassName(classId), "\")", (char *)NULL); + } + return TCL_ERROR; + } + penPtr->refCount++; + *penPtrPtr = penPtr; + return TCL_OK; +} + + +/* + *--------------------------------------------------------------------------- + * + * Blt_DestroyPens -- + * + * Release memory and resources allocated for the style. + * + * Results: + * None. + * + * Side effects: + * Everything associated with the pen style is freed up. + * + *--------------------------------------------------------------------------- + */ +void +Blt_DestroyPens(Graph *graphPtr) +{ + Blt_HashEntry *hPtr; + Blt_HashSearch iter; + + for (hPtr = Blt_FirstHashEntry(&graphPtr->penTable, &iter); + hPtr != NULL; hPtr = Blt_NextHashEntry(&iter)) { + Pen *penPtr; + + penPtr = Blt_GetHashValue(hPtr); + penPtr->hashPtr = NULL; + DestroyPen(penPtr); + } + Blt_DeleteHashTable(&graphPtr->penTable); +} + +/* + *--------------------------------------------------------------------------- + * + * CgetOp -- + * + * Queries axis attributes (font, line width, label, etc). + * + * Results: + * A standard TCL result. If querying configuration values, + * interp->result will contain the results. + * + *--------------------------------------------------------------------------- + */ +/* ARGSUSED */ +static int +CgetOp(Tcl_Interp *interp, Graph *graphPtr, int objc, Tcl_Obj *const *objv) +{ + Pen *penPtr; + unsigned int configFlags; + + if (GetPenFromObj(interp, graphPtr, objv[3], &penPtr) != TCL_OK) { + return TCL_ERROR; + } + configFlags = (penPtr->flags & (ACTIVE_PEN | NORMAL_PEN)); + return Blt_ConfigureValueFromObj(interp, graphPtr->tkwin, + penPtr->configSpecs, (char *)penPtr, objv[4], configFlags); +} + +/* + *--------------------------------------------------------------------------- + * + * ConfigureOp -- + * + * Queries or resets pen attributes (font, line width, color, etc). + * + * Results: + * A standard TCL result. If querying configuration values, + * interp->result will contain the results. + * + * Side Effects: + * Pen resources are possibly allocated (GC, font). + * + *--------------------------------------------------------------------------- + */ +static int +ConfigureOp(Tcl_Interp *interp, Graph *graphPtr, int objc, Tcl_Obj *const *objv) +{ + Pen *penPtr; + int nNames, nOpts; + int redraw; + Tcl_Obj *const *options; + int i; + + /* Figure out where the option value pairs begin */ + objc -= 3; + objv += 3; + for (i = 0; i < objc; i++) { + char *string; + + string = Tcl_GetString(objv[i]); + if (string[0] == '-') { + break; + } + if (GetPenFromObj(interp, graphPtr, objv[i], &penPtr) != TCL_OK) { + return TCL_ERROR; + } + } + nNames = i; /* Number of pen names specified */ + nOpts = objc - i; /* Number of options specified */ + options = objv + i; /* Start of options in objv */ + + redraw = 0; + for (i = 0; i < nNames; i++) { + int flags; + + if (GetPenFromObj(interp, graphPtr, objv[i], &penPtr) != TCL_OK) { + return TCL_ERROR; + } + flags = BLT_CONFIG_OBJV_ONLY | (penPtr->flags&(ACTIVE_PEN|NORMAL_PEN)); + if (nOpts == 0) { + return Blt_ConfigureInfoFromObj(interp, graphPtr->tkwin, + penPtr->configSpecs, (char *)penPtr, (Tcl_Obj *)NULL, flags); + } else if (nOpts == 1) { + return Blt_ConfigureInfoFromObj(interp, graphPtr->tkwin, + penPtr->configSpecs, (char *)penPtr, options[0], flags); + } + if (Blt_ConfigureWidgetFromObj(interp, graphPtr->tkwin, + penPtr->configSpecs, nOpts, options, (char *)penPtr, flags) + != TCL_OK) { + break; + } + (*penPtr->configProc) (graphPtr, penPtr); + if (penPtr->refCount > 0) { + redraw++; + } + } + if (redraw) { + graphPtr->flags |= CACHE_DIRTY; + Blt_EventuallyRedrawGraph(graphPtr); + } + if (i < nNames) { + return TCL_ERROR; + } + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * CreateOp -- + * + * Adds a new penstyle to the graph. + * + * Results: + * A standard TCL result. + * + *--------------------------------------------------------------------------- + */ +static int +CreateOp(Tcl_Interp *interp, Graph *graphPtr, int objc, Tcl_Obj *const *objv) +{ + Pen *penPtr; + + penPtr = Blt_CreatePen(graphPtr, Tcl_GetString(objv[3]), graphPtr->classId, + objc - 4, objv + 4); + if (penPtr == NULL) { + return TCL_ERROR; + } + Tcl_SetObjResult(interp, objv[3]); + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * DeleteOp -- + * + * Delete the given pen. + * + * Results: + * Always returns TCL_OK. The interp->result field is a list of the + * graph axis limits. + * + *--------------------------------------------------------------------------- + */ + +/*ARGSUSED*/ +static int +DeleteOp(Tcl_Interp *interp, Graph *graphPtr, int objc, Tcl_Obj *const *objv) +{ + int i; + + for (i = 3; i < objc; i++) { + Pen *penPtr; + + if (GetPenFromObj(interp, graphPtr, objv[i], &penPtr) != TCL_OK) { + return TCL_ERROR; + } + if (penPtr->flags & DELETE_PENDING) { + Tcl_AppendResult(interp, "can't find pen \"", + Tcl_GetString(objv[i]), "\" in \"", + Tk_PathName(graphPtr->tkwin), "\"", (char *)NULL); + return TCL_ERROR; + } + penPtr->flags |= DELETE_PENDING; + if (penPtr->refCount == 0) { + DestroyPen(penPtr); + } + } + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * NamesOp -- + * + * Return a list of the names of all the axes. + * + * Results: + * Returns a standard TCL result. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +NamesOp(Tcl_Interp *interp, Graph *graphPtr, int objc, Tcl_Obj *const *objv) +{ + Tcl_Obj *listObjPtr; + + listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL); + if (objc == 3) { + Blt_HashEntry *hPtr; + Blt_HashSearch iter; + + for (hPtr = Blt_FirstHashEntry(&graphPtr->penTable, &iter); + hPtr != NULL; hPtr = Blt_NextHashEntry(&iter)) { + Pen *penPtr; + + penPtr = Blt_GetHashValue(hPtr); + if ((penPtr->flags & DELETE_PENDING) == 0) { + Tcl_ListObjAppendElement(interp, listObjPtr, + Tcl_NewStringObj(penPtr->name, -1)); + } + } + } else { + Blt_HashEntry *hPtr; + Blt_HashSearch iter; + + for (hPtr = Blt_FirstHashEntry(&graphPtr->penTable, &iter); + hPtr != NULL; hPtr = Blt_NextHashEntry(&iter)) { + Pen *penPtr; + + penPtr = Blt_GetHashValue(hPtr); + if ((penPtr->flags & DELETE_PENDING) == 0) { + int i; + + for (i = 3; i < objc; i++) { + char *pattern; + + pattern = Tcl_GetString(objv[i]); + if (Tcl_StringMatch(penPtr->name, pattern)) { + Tcl_ListObjAppendElement(interp, listObjPtr, + Tcl_NewStringObj(penPtr->name, -1)); + break; + } + } + } + } + } + Tcl_SetObjResult(interp, listObjPtr); + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * TypeOp -- + * + * Return the type of pen. + * + * Results: + * Returns a standard TCL result. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +TypeOp(Tcl_Interp *interp, Graph *graphPtr, int objc, Tcl_Obj *const *objv) +{ + Pen *penPtr; + + if (GetPenFromObj(interp, graphPtr, objv[3], &penPtr) != TCL_OK) { + return TCL_ERROR; + } + Tcl_SetStringObj(Tcl_GetObjResult(interp), + Blt_GraphClassName(penPtr->classId), -1); + return TCL_OK; +} + +static Blt_OpSpec penOps[] = +{ + {"cget", 2, CgetOp, 5, 5, "penName option",}, + {"configure", 2, ConfigureOp, 4, 0, + "penName ?penName?... ?option value?...",}, + {"create", 2, CreateOp, 4, 0, "penName ?option value?...",}, + {"delete", 2, DeleteOp, 3, 0, "?penName?...",}, + {"names", 1, NamesOp, 3, 0, "?pattern?...",}, + {"type", 1, TypeOp, 4, 4, "penName",}, +}; +static int nPenOps = sizeof(penOps) / sizeof(Blt_OpSpec); + +int +Blt_PenOp(Graph *graphPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) +{ + GraphPenProc *proc; + + proc = Blt_GetOpFromObj(interp, nPenOps, penOps, BLT_OP_ARG2, + objc, objv, 0); + if (proc == NULL) { + return TCL_ERROR; + } + return (*proc) (interp, graphPtr, objc, objv); +} diff --git a/src/bltGrText.C b/src/bltGrText.C new file mode 100644 index 0000000..d9cb33b --- /dev/null +++ b/src/bltGrText.C @@ -0,0 +1,2018 @@ + +/* + * bltText.c -- + * + * This module implements multi-line, rotate-able text for the BLT toolkit. + * + * Copyright 1993-2004 George A Howlett. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include "bltInt.h" +#include <bltHash.h> +#include <X11/Xutil.h> +#include "tkIntBorder.h" +#include "tkDisplay.h" +#include "bltImage.h" +#include "bltBitmap.h" +#include "bltFont.h" +#include "bltText.h" +#include "bltBgStyle.h" + +#define WINDEBUG 0 + +static Blt_HashTable bitmapGCTable; +static int initialized; + +GC +Blt_GetBitmapGC(Tk_Window tkwin) +{ + int isNew; + GC gc; + Display *display; + Blt_HashEntry *hPtr; + + if (!initialized) { + Blt_InitHashTable(&bitmapGCTable, BLT_ONE_WORD_KEYS); + initialized = TRUE; + } + display = Tk_Display(tkwin); + hPtr = Blt_CreateHashEntry(&bitmapGCTable, (char *)display, &isNew); + if (isNew) { + Pixmap bitmap; + XGCValues gcValues; + unsigned long gcMask; + Window root; + + root = Tk_RootWindow(tkwin); + bitmap = Tk_GetPixmap(display, root, 1, 1, 1); + gcValues.foreground = gcValues.background = 0; + gcMask = (GCForeground | GCBackground); + gc = Blt_GetPrivateGCFromDrawable(display, bitmap, gcMask, &gcValues); + Tk_FreePixmap(display, bitmap); + Blt_SetHashValue(hPtr, gc); + } else { + gc = (GC)Blt_GetHashValue(hPtr); + } + return gc; +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_GetTextExtents -- + * + * Get the extents of a possibly multiple-lined text string. + * + * Results: + * Returns via *widthPtr* and *heightPtr* the dimensions of the text + * string. + * + *--------------------------------------------------------------------------- + */ +void +Blt_GetTextExtents( + Blt_Font font, + int leader, + const char *text, /* Text string to be measured. */ + int textLen, /* Length of the text. If -1, indicates that + * text is an ASCIZ string that the length + * should be computed with strlen. */ + unsigned int *widthPtr, + unsigned int *heightPtr) +{ + unsigned int lineHeight; + + if (text == NULL) { + return; /* NULL string? */ + } + { + Blt_FontMetrics fm; + + Blt_GetFontMetrics(font, &fm); + lineHeight = fm.linespace; + } + if (textLen < 0) { + textLen = strlen(text); + } + { + unsigned int lineLen; /* # of characters on each line */ + const char *p, *pend; + const char *line; + unsigned int maxWidth, maxHeight; + + maxWidth = maxHeight = 0; + lineLen = 0; + for (p = line = text, pend = text + textLen; p < pend; p++) { + if (*p == '\n') { + if (lineLen > 0) { + unsigned int lineWidth; + + lineWidth = Blt_TextWidth(font, line, lineLen); + if (lineWidth > maxWidth) { + maxWidth = lineWidth; + } + } + maxHeight += lineHeight; + line = p + 1; /* Point to the start of the next line. */ + lineLen = 0; /* Reset counter to indicate the start of a + * new line. */ + continue; + } + lineLen++; + } + if ((lineLen > 0) && (*(p - 1) != '\n')) { + unsigned int lineWidth; + + maxHeight += lineHeight; + lineWidth = Blt_TextWidth(font, line, lineLen); + if (lineWidth > maxWidth) { + maxWidth = lineWidth; + } + } + *widthPtr = maxWidth; + *heightPtr = maxHeight; + } +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_Ts_GetExtents -- + * + * Get the extents of a possibly multiple-lined text string. + * + * Results: + * Returns via *widthPtr* and *heightPtr* the dimensions of + * the text string. + * + *--------------------------------------------------------------------------- + */ +void +Blt_Ts_GetExtents(TextStyle *tsPtr, const char *text, unsigned int *widthPtr, + unsigned int *heightPtr) +{ + + if (text == NULL) { + *widthPtr = *heightPtr = 0; + } else { + unsigned int w, h; + + Blt_GetTextExtents(tsPtr->font, tsPtr->leader, text, -1, &w, &h); + *widthPtr = w + PADDING(tsPtr->xPad); + *heightPtr = h + PADDING(tsPtr->yPad); + } +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_GetBoundingBox + * + * Computes the dimensions of the bounding box surrounding a rectangle + * rotated about its center. If pointArr isn't NULL, the coordinates of + * the rotated rectangle are also returned. + * + * The dimensions are determined by rotating the rectangle, and doubling + * the maximum x-coordinate and y-coordinate. + * + * w = 2 * maxX, h = 2 * maxY + * + * Since the rectangle is centered at 0,0, the coordinates of the + * bounding box are (-w/2,-h/2 w/2,-h/2, w/2,h/2 -w/2,h/2). + * + * 0 ------- 1 + * | | + * | x | + * | | + * 3 ------- 2 + * + * Results: + * The width and height of the bounding box containing the rotated + * rectangle are returned. + * + *--------------------------------------------------------------------------- + */ +void +Blt_GetBoundingBox( + int width, int height, /* Unrotated region */ + float angle, /* Rotation of box */ + double *rotWidthPtr, + double *rotHeightPtr, /* (out) Bounding box region */ + Point2d *bbox) /* (out) Points of the rotated box */ +{ + int i; + double sinTheta, cosTheta; + double radians; + double xMax, yMax; + double x, y; + Point2d corner[4]; + + angle = FMOD(angle, 360.0); + if (FMOD(angle, (double)90.0) == 0.0) { + int ll, ur, ul, lr; + double rotWidth, rotHeight; + int quadrant; + + /* Handle right-angle rotations specially. */ + + quadrant = (int)(angle / 90.0); + switch (quadrant) { + case ROTATE_270: /* 270 degrees */ + ul = 3, ur = 0, lr = 1, ll = 2; + rotWidth = (double)height; + rotHeight = (double)width; + break; + case ROTATE_90: /* 90 degrees */ + ul = 1, ur = 2, lr = 3, ll = 0; + rotWidth = (double)height; + rotHeight = (double)width; + break; + case ROTATE_180: /* 180 degrees */ + ul = 2, ur = 3, lr = 0, ll = 1; + rotWidth = (double)width; + rotHeight = (double)height; + break; + default: + case ROTATE_0: /* 0 degrees */ + ul = 0, ur = 1, lr = 2, ll = 3; + rotWidth = (double)width; + rotHeight = (double)height; + break; + } + if (bbox != NULL) { + x = rotWidth * 0.5; + y = rotHeight * 0.5; + bbox[ll].x = bbox[ul].x = -x; + bbox[ur].y = bbox[ul].y = -y; + bbox[lr].x = bbox[ur].x = x; + bbox[ll].y = bbox[lr].y = y; + } + *rotWidthPtr = rotWidth; + *rotHeightPtr = rotHeight; + return; + } + /* Set the four corners of the rectangle whose center is the origin. */ + corner[1].x = corner[2].x = (double)width * 0.5; + corner[0].x = corner[3].x = -corner[1].x; + corner[2].y = corner[3].y = (double)height * 0.5; + corner[0].y = corner[1].y = -corner[2].y; + + radians = (-angle / 180.0) * M_PI; + sinTheta = sin(radians), cosTheta = cos(radians); + xMax = yMax = 0.0; + + /* Rotate the four corners and find the maximum X and Y coordinates */ + + for (i = 0; i < 4; i++) { + x = (corner[i].x * cosTheta) - (corner[i].y * sinTheta); + y = (corner[i].x * sinTheta) + (corner[i].y * cosTheta); + if (x > xMax) { + xMax = x; + } + if (y > yMax) { + yMax = y; + } + if (bbox != NULL) { + bbox[i].x = x; + bbox[i].y = y; + } + } + + /* + * By symmetry, the width and height of the bounding box are twice the + * maximum x and y coordinates. + */ + *rotWidthPtr = xMax + xMax; + *rotHeightPtr = yMax + yMax; +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_TranslateAnchor -- + * + * Translate the coordinates of a given bounding box based upon the + * anchor specified. The anchor indicates where the given x-y position + * is in relation to the bounding box. + * + * nw --- n --- ne + * | | + * w center e + * | | + * sw --- s --- se + * + * The coordinates returned are translated to the origin of the bounding + * box (suitable for giving to XCopyArea, XCopyPlane, etc.) + * + * Results: + * The translated coordinates of the bounding box are returned. + * + *--------------------------------------------------------------------------- + */ +void +Blt_TranslateAnchor( + int x, int y, /* Window coordinates of anchor */ + int w, int h, /* Extents of the bounding box */ + Tk_Anchor anchor, /* Direction of the anchor */ + int *xPtr, int *yPtr) +{ + switch (anchor) { + case TK_ANCHOR_NW: /* Upper left corner */ + break; + case TK_ANCHOR_W: /* Left center */ + y -= (h / 2); + break; + case TK_ANCHOR_SW: /* Lower left corner */ + y -= h; + break; + case TK_ANCHOR_N: /* Top center */ + x -= (w / 2); + break; + case TK_ANCHOR_CENTER: /* Center */ + x -= (w / 2); + y -= (h / 2); + break; + case TK_ANCHOR_S: /* Bottom center */ + x -= (w / 2); + y -= h; + break; + case TK_ANCHOR_NE: /* Upper right corner */ + x -= w; + break; + case TK_ANCHOR_E: /* Right center */ + x -= w; + y -= (h / 2); + break; + case TK_ANCHOR_SE: /* Lower right corner */ + x -= w; + y -= h; + break; + } + *xPtr = x; + *yPtr = y; +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_AnchorPoint -- + * + * Translates a position, using both the dimensions of the bounding box, + * and the anchor direction, returning the coordinates of the upper-left + * corner of the box. The anchor indicates where the given x-y position + * is in relation to the bounding box. + * + * nw --- n --- ne + * | | + * w center e + * | | + * sw --- s --- se + * + * The coordinates returned are translated to the origin of the bounding + * box (suitable for giving to XCopyArea, XCopyPlane, etc.) + * + * Results: + * The translated coordinates of the bounding box are returned. + * + *--------------------------------------------------------------------------- + */ +Point2d +Blt_AnchorPoint( + double x, double y, /* Coordinates of anchor. */ + double w, double h, /* Extents of the bounding box */ + Tk_Anchor anchor) /* Direction of the anchor */ +{ + Point2d t; + + switch (anchor) { + case TK_ANCHOR_NW: /* Upper left corner */ + break; + case TK_ANCHOR_W: /* Left center */ + y -= (h * 0.5); + break; + case TK_ANCHOR_SW: /* Lower left corner */ + y -= h; + break; + case TK_ANCHOR_N: /* Top center */ + x -= (w * 0.5); + break; + case TK_ANCHOR_CENTER: /* Center */ + x -= (w * 0.5); + y -= (h * 0.5); + break; + case TK_ANCHOR_S: /* Bottom center */ + x -= (w * 0.5); + y -= h; + break; + case TK_ANCHOR_NE: /* Upper right corner */ + x -= w; + break; + case TK_ANCHOR_E: /* Right center */ + x -= w; + y -= (h * 0.5); + break; + case TK_ANCHOR_SE: /* Lower right corner */ + x -= w; + y -= h; + break; + } + t.x = x; + t.y = y; + return t; +} + +static INLINE int +SizeOfUtfChar(const char *s) /* Buffer in which the UTF-8 representation of + * the Tcl_UniChar is stored. Buffer must be + * large enough to hold the UTF-8 character + * (at most TCL_UTF_MAX bytes). */ +{ + int byte; + + byte = *((unsigned char *)s); + if (byte < 0xC0) { + return 1; + } else if ((byte < 0xE0) && ((s[1] & 0xC0) == 0x80)) { + return 2; + } else if ((byte < 0xF0) && + ((s[1] & 0xC0) == 0x80) && ((s[2] & 0xC0) == 0x80)) { + return 3; + } + return 1; +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_MeasureText -- + * + * Draw a string of characters on the screen. Blt_DrawChars() + * expands control characters that occur in the string to + * \xNN sequences. + * + * Results: + * None. + * + * Side effects: + * Information gets drawn on the screen. + * + *--------------------------------------------------------------------------- + */ +int +Blt_MeasureText( + Blt_Font font, /* Font in which characters will be drawn; + * must be the same as font used in GC. */ + const char *text, /* UTF-8 string to be displayed. Need not be + * '\0' terminated. All Tk meta-characters + * (tabs, control characters, and newlines) + * should be stripped out of the string that + * is passed to this function. If they are + * not stripped out, they will be displayed as + * regular printing characters. */ + int textLen, /* # of bytes to draw in text string. */ + int maxLength, + int *countPtr) +{ + int elWidth; + const char *s, *send; + int accum, count, threshold; + int nBytes; + + if (maxLength < 0) { + if (countPtr != NULL) { + nBytes = textLen; + } + return Blt_TextWidth(font, text, textLen); + } + elWidth = Blt_TextWidth(font, "...", 3); + threshold = maxLength - elWidth; + if (threshold <= 0) { + return 0; + } +#if !HAVE_UTF + nBytes = 1; +#endif /* !HAVE_UTF */ + count = accum = 0; + for (s = text, send = s + textLen; s < send; s += nBytes) { +#if HAVE_UTF + Tcl_UniChar ch; +#endif /* HAVE_UTF */ + int w; +#if HAVE_UTF + nBytes = Tcl_UtfToUniChar (s, &ch); +#endif /* HAVE_UTF */ + w = Blt_TextWidth(font, s, nBytes); + if ((accum + w) > threshold) { + if (countPtr != NULL) { + *countPtr = count; + } + return accum + elWidth; + } + accum += w; + count += nBytes; + } + if (countPtr != NULL) { + *countPtr = count; + } + return accum; +} + + +/* + *--------------------------------------------------------------------------- + * + * Blt_Ts_CreateLayout -- + * + * Get the extents of a possibly multiple-lined text string. + * + * Results: + * Returns via *widthPtr* and *heightPtr* the dimensions of the text + * string. + * + *--------------------------------------------------------------------------- + */ +TextLayout * +Blt_Ts_CreateLayout(const char *text, int textLen, TextStyle *tsPtr) +{ + TextFragment *fp; + TextLayout *layoutPtr; + Blt_FontMetrics fm; + int count; /* Count # of characters on each line */ + int lineHeight; + size_t maxHeight, maxWidth; + size_t nFrags; + int width; /* Running dimensions of the text */ + const char *p, *endp, *start; + int i; + size_t size; + + nFrags = 0; + endp = text + ((textLen < 0) ? strlen(text) : textLen); + for (p = text; p < endp; p++) { + if (*p == '\n') { + nFrags++; + } + } + if ((p != text) && (*(p - 1) != '\n')) { + nFrags++; + } + size = sizeof(TextLayout) + (sizeof(TextFragment) * (nFrags - 1)); + + layoutPtr = Blt_AssertCalloc(1, size); + layoutPtr->nFrags = nFrags; + + nFrags = count = 0; + width = maxWidth = 0; + maxHeight = tsPtr->padTop; + Blt_GetFontMetrics(tsPtr->font, &fm); + lineHeight = fm.linespace + tsPtr->leader; + + fp = layoutPtr->fragments; + for (p = start = text; p < endp; p++) { + if (*p == '\n') { + if (count > 0) { + width = Blt_TextWidth(tsPtr->font, start, count); + if (width > maxWidth) { + maxWidth = width; + } + } else { + width = 0; + } + fp->width = width; + fp->count = count; + fp->sy = fp->y = maxHeight + fm.ascent; + fp->text = start; + maxHeight += lineHeight; + fp++; + nFrags++; + start = p + 1; /* Start the text on the next line */ + count = 0; /* Reset to indicate the start of a new + * line */ + continue; + } + count++; + } + + if (nFrags < layoutPtr->nFrags) { + width = Blt_TextWidth(tsPtr->font, start, count); + if (width > maxWidth) { + maxWidth = width; + } + fp->width = width; + fp->count = count; + fp->sy = fp->y = maxHeight + fm.ascent; + fp->text = start; + maxHeight += lineHeight; + nFrags++; + } + maxHeight += tsPtr->padBottom; + maxWidth += PADDING(tsPtr->xPad); + fp = layoutPtr->fragments; + for (i = 0; i < nFrags; i++, fp++) { + switch (tsPtr->justify) { + default: + case TK_JUSTIFY_LEFT: + /* No offset for left justified text strings */ + fp->x = fp->sx = tsPtr->padLeft; + break; + case TK_JUSTIFY_RIGHT: + fp->x = fp->sx = (maxWidth - fp->width) - tsPtr->padRight; + break; + case TK_JUSTIFY_CENTER: + fp->x = fp->sx = (maxWidth - fp->width) / 2; + break; + } + } + if (tsPtr->underline >= 0) { + fp = layoutPtr->fragments; + for (i = 0; i < nFrags; i++, fp++) { + int first, last; + + first = fp->text - text; + last = first + fp->count; + if ((tsPtr->underline >= first) && (tsPtr->underline < last)) { + layoutPtr->underlinePtr = fp; + layoutPtr->underline = tsPtr->underline - first; + break; + } + } + } + layoutPtr->width = maxWidth; + layoutPtr->height = maxHeight - tsPtr->leader; + return layoutPtr; +} + + +/* + *--------------------------------------------------------------------------- + * + * Blt_DrawCharsWithEllipsis -- + * + * Draw a string of characters on the screen. Blt_DrawChars() + * expands control characters that occur in the string to + * \xNN sequences. + * + * Results: + * None. + * + * Side effects: + * Information gets drawn on the screen. + * + *--------------------------------------------------------------------------- + */ +void +Blt_DrawCharsWithEllipsis( + Tk_Window tkwin, /* Display on which to draw. */ + Drawable drawable, /* Window or pixmap in which to draw. */ + GC gc, /* Graphics context for drawing characters. */ + Blt_Font font, /* Font in which characters will be drawn; + * must be the same as font used in GC. */ + int depth, + float angle, + const char *text, /* UTF-8 string to be displayed. Need not be + * '\0' terminated. All Tk meta-characters + * (tabs, control characters, and newlines) + * should be stripped out of the string that + * is passed to this function. If they are + * not stripped out, they will be displayed as + * regular printing characters. */ + int textLen, /* # of bytes to draw in text string. */ + int x, int y, /* Coordinates at which to place origin of + * string when drawing. */ + int maxLength) +{ + int elWidth; + const char *s, *send; + Tcl_DString dString; + int nBytes; + int accum, threshold; + +#if HAVE_UTF + Tcl_UniChar ch; +#endif /* HAVE_UTF */ + accum = 0; + elWidth = Blt_TextWidth(font, "...", 3); + if (maxLength < elWidth) { + return; + } + threshold = maxLength - elWidth; + Tcl_DStringInit(&dString); +#if !HAVE_UTF + nBytes = 1; +#endif /* !HAVE_UTF */ + for (s = text, send = s + textLen; s < send; s += nBytes) { +#if HAVE_UTF + nBytes = Tcl_UtfToUniChar (s, &ch); +#endif /* HAVE_UTF */ + accum += Blt_TextWidth(font, s, nBytes); + if (accum > threshold) { + break; + } + Tcl_DStringAppend(&dString, s, nBytes); + } + if (s < send) { + Tcl_DStringAppend(&dString, "...", 3); + } + Blt_DrawChars(Tk_Display(tkwin), drawable, gc, font, depth, angle, + Tcl_DStringValue(&dString), Tcl_DStringLength(&dString), x, y); + Tcl_DStringFree(&dString); +} + +void +Blt_DrawLayout(Tk_Window tkwin, Drawable drawable, GC gc, Blt_Font font, + int depth, float angle, int x, int y, TextLayout *layoutPtr, + int maxLength) +{ + TextFragment *fp, *fend; + Blt_FontMetrics fm; + + Blt_GetFontMetrics(font, &fm); + for (fp = layoutPtr->fragments, fend = fp + layoutPtr->nFrags; + fp < fend; fp++) { + int sx, sy; + + sx = x + fp->sx, sy = y + fp->sy; + if ((maxLength > 0) && ((fp->width + fp->x) > maxLength)) { + Blt_DrawCharsWithEllipsis(tkwin, drawable, gc, font, depth, angle, + fp->text, fp->count, sx, sy, maxLength - fp->x); + } else { + Blt_DrawChars(Tk_Display(tkwin), drawable, gc, font, depth, angle, + fp->text, fp->count, sx, sy); + } + } + if (layoutPtr->underlinePtr != NULL) { + fp = layoutPtr->underlinePtr; + Blt_UnderlineChars(Tk_Display(tkwin), drawable, gc, font, fp->text, + fp->count, x + fp->sx, y + fp->sy, layoutPtr->underline, + layoutPtr->underline + 1, maxLength); + } +} + + +#ifdef WIN32 +/* + *--------------------------------------------------------------------------- + * + * Blt_Ts_Bitmap -- + * + * Draw a bitmap, using the the given window coordinates as an anchor for + * the text bounding box. + * + * Results: + * Returns the bitmap representing the text string. + * + * Side Effects: + * Bitmap is drawn using the given font and GC in the drawable at the + * given coordinates, anchor, and rotation. + * + *--------------------------------------------------------------------------- + */ +Pixmap +Blt_Ts_Bitmap( + Tk_Window tkwin, + TextLayout *layoutPtr, /* Text string to draw */ + TextStyle *stylePtr, /* Text attributes: rotation, color, + * font, linespacing, justification, + * etc. */ + int *bmWidthPtr, + int *bmHeightPtr) /* Extents of rotated text string */ +{ + Pixmap bitmap; + Window root; + GC gc; + HDC hDC; + TkWinDCState state; + + /* Create a temporary bitmap to contain the text string */ + root = Tk_RootWindow(tkwin); + bitmap = Tk_GetPixmap(Tk_Display(tkwin), root, layoutPtr->width, + layoutPtr->height, 1); + assert(bitmap != None); + if (bitmap == None) { + return None; /* Can't allocate pixmap. */ + } + gc = Blt_GetBitmapGC(tkwin); + + /* Clear the pixmap and draw the text string into it */ + hDC = TkWinGetDrawableDC(Tk_Display(tkwin), bitmap, &state); + PatBlt(hDC, 0, 0, layoutPtr->width, layoutPtr->height, WHITENESS); + TkWinReleaseDrawableDC(bitmap, hDC, &state); + + XSetFont(Tk_Display(tkwin), gc, Blt_FontId(stylePtr->font)); + XSetForeground(Tk_Display(tkwin), gc, 1); + Blt_DrawLayout(tkwin, bitmap, gc, stylePtr->font, 1, 0.0f, 0, 0, layoutPtr, + stylePtr->maxLength); + + /* + * Under Win32, 1 is off and 0 is on. That's why we're inverting the + * bitmap here. + */ + hDC = TkWinGetDrawableDC(Tk_Display(tkwin), bitmap, &state); + PatBlt(hDC, 0, 0, layoutPtr->width, layoutPtr->height, DSTINVERT); + TkWinReleaseDrawableDC(bitmap, hDC, &state); + + *bmWidthPtr = layoutPtr->width, *bmHeightPtr = layoutPtr->height; + return bitmap; +} +#else +/* + *--------------------------------------------------------------------------- + * + * Blt_Ts_Bitmap -- + * + * Draw a bitmap, using the the given window coordinates as an anchor for + * the text bounding box. + * + * Results: + * Returns the bitmap representing the text string. + * + * Side Effects: + * Bitmap is drawn using the given font and GC in the drawable at the + * given coordinates, anchor, and rotation. + * + *--------------------------------------------------------------------------- + */ +Pixmap +Blt_Ts_Bitmap( + Tk_Window tkwin, + TextLayout *layoutPtr, /* Text string to draw */ + TextStyle *stylePtr, /* Text attributes: rotation, color, + * font, linespacing, justification, + * etc. */ + int *bmWidthPtr, + int *bmHeightPtr) /* Extents of rotated text string */ +{ + Pixmap bitmap; + GC gc; + + /* Create a bitmap big enough to contain the text. */ + bitmap = Tk_GetPixmap(Tk_Display(tkwin), Tk_RootWindow(tkwin), + layoutPtr->width, layoutPtr->height, 1); + assert(bitmap != None); + if (bitmap == None) { + return None; /* Can't allocate pixmap. */ + } + gc = Blt_GetBitmapGC(tkwin); + + /* Clear the bitmap. Background is 0. */ + XSetForeground(Tk_Display(tkwin), gc, 0); + XFillRectangle(Tk_Display(tkwin), bitmap, gc, 0, 0, layoutPtr->width, + layoutPtr->height); + + /* Draw the text into the bitmap. Foreground is 1. */ + XSetFont(Tk_Display(tkwin), gc, Blt_FontId(stylePtr->font)); + XSetForeground(Tk_Display(tkwin), gc, 1); + Blt_DrawLayout(tkwin, bitmap, gc, stylePtr->font, 1, 0.0f, 0, 0, layoutPtr, + stylePtr->maxLength); + *bmWidthPtr = layoutPtr->width, *bmHeightPtr = layoutPtr->height; + return bitmap; +} +#endif /* WIN32 */ + +void +Blt_Ts_SetDrawStyle( + TextStyle *stylePtr, + Blt_Font font, + GC gc, + XColor *normalColor, + float angle, + Tk_Anchor anchor, + Tk_Justify justify, + int leader) +{ + stylePtr->xPad.side1 = stylePtr->xPad.side2 = 0; + stylePtr->yPad.side1 = stylePtr->yPad.side2 = 0; + stylePtr->state = 0; + stylePtr->anchor = anchor; + stylePtr->color = normalColor; + stylePtr->font = font; + stylePtr->gc = gc; + stylePtr->justify = justify; + stylePtr->leader = leader; + stylePtr->angle = (float)angle; +} + +static void +DrawStandardLayout(Tk_Window tkwin, Drawable drawable, TextStyle *stylePtr, + TextLayout *layoutPtr, int x, int y) +{ + int w, h; + /* + * This is the easy case of no rotation. Simply draw the text + * using the standard drawing routines. Handle offset printing + * for engraved (disabled) text. + */ + w = layoutPtr->width; + h = layoutPtr->height; + if ((stylePtr->maxLength > 0) && (stylePtr->maxLength < w)) { + w = stylePtr->maxLength; + } + Blt_TranslateAnchor(x, y, w, h, stylePtr->anchor, &x, &y); + if (stylePtr->state & (STATE_DISABLED | STATE_EMPHASIS)) { + TkBorder *borderPtr = (TkBorder *) Blt_BackgroundBorder(stylePtr->bg); + XColor *color1, *color2; + + color1 = borderPtr->lightColor, color2 = borderPtr->darkColor; + if (stylePtr->state & STATE_EMPHASIS) { + XColor *hold; + + hold = color1, color1 = color2, color2 = hold; + } + if (color1 != NULL) { + XSetForeground(Tk_Display(tkwin), stylePtr->gc, color1->pixel); + } + Blt_DrawLayout(tkwin, drawable, stylePtr->gc, stylePtr->font, + Tk_Depth(tkwin), 0.0f, x+1, y+1, layoutPtr,stylePtr->maxLength); + if (color2 != NULL) { + XSetForeground(Tk_Display(tkwin), stylePtr->gc, color2->pixel); + } + Blt_DrawLayout(tkwin, drawable, stylePtr->gc, stylePtr->font, + Tk_Depth(tkwin), 0.0f, x, y, layoutPtr, stylePtr->maxLength); + + /* Reset the foreground color back to its original setting, so not to + * invalidate the GC cache. */ + XSetForeground(Tk_Display(tkwin), stylePtr->gc, stylePtr->color->pixel); + + return; /* Done */ + } + Blt_DrawLayout(tkwin, drawable, stylePtr->gc, stylePtr->font, + Tk_Depth(tkwin), 0.0f, x, y, layoutPtr, stylePtr->maxLength); +} + + +static void +RotateStartingTextPositions(TextLayout *lPtr, int w, int h, float angle) +{ + Point2d off1, off2; + TextFragment *fp, *fend; + double radians; + double rw, rh; + double sinTheta, cosTheta; + + Blt_GetBoundingBox(w, h, angle, &rw, &rh, (Point2d *)NULL); + off1.x = (double)w * 0.5; + off1.y = (double)h * 0.5; + off2.x = rw * 0.5; + off2.y = rh * 0.5; + radians = (-angle / 180.0) * M_PI; + + sinTheta = sin(radians), cosTheta = cos(radians); + for (fp = lPtr->fragments, fend = fp + lPtr->nFrags; fp < fend; fp++) { + Point2d p, q; + + p.x = fp->x - off1.x; + p.y = fp->y - off1.y; + q.x = (p.x * cosTheta) - (p.y * sinTheta); + q.y = (p.x * sinTheta) + (p.y * cosTheta); + q.x += off2.x; + q.y += off2.y; + fp->sx = ROUND(q.x); + fp->sy = ROUND(q.y); + } +} + +void +Blt_RotateStartingTextPositions(TextLayout *lPtr, float angle) +{ + RotateStartingTextPositions(lPtr, lPtr->width, lPtr->height, angle); +} + +int +Blt_DrawTextWithRotatedFont(Tk_Window tkwin, Drawable drawable, float angle, + TextStyle *stylePtr, TextLayout *layoutPtr, + int x, int y) +{ + double rw, rh; + int w, h; + + w = layoutPtr->width; + h = layoutPtr->height; + if ((stylePtr->maxLength > 0) && (stylePtr->maxLength < w)) { + w = stylePtr->maxLength; + } + RotateStartingTextPositions(layoutPtr, w, h, angle); + Blt_GetBoundingBox(w, h, angle, &rw, &rh, (Point2d *)NULL); + Blt_TranslateAnchor(x, y, (int)rw, (int)rh, stylePtr->anchor, &x, &y); + if (stylePtr->state & (STATE_DISABLED | STATE_EMPHASIS)) { + TkBorder *borderPtr = (TkBorder *)Blt_BackgroundBorder(stylePtr->bg); + XColor *color1, *color2; + + color1 = borderPtr->lightColor, color2 = borderPtr->darkColor; + if (stylePtr->state & STATE_EMPHASIS) { + XColor *hold; + + hold = color1, color1 = color2, color2 = hold; + } + if (color1 != NULL) { + XSetForeground(Tk_Display(tkwin), stylePtr->gc, color1->pixel); + Blt_DrawLayout(tkwin, drawable, stylePtr->gc, stylePtr->font, + Tk_Depth(tkwin), angle, x, y, layoutPtr, stylePtr->maxLength); + } + if (color2 != NULL) { + XSetForeground(Tk_Display(tkwin), stylePtr->gc, color2->pixel); + Blt_DrawLayout(tkwin, drawable, stylePtr->gc, stylePtr->font, + Tk_Depth(tkwin), angle, x, y, layoutPtr, stylePtr->maxLength); + } + XSetForeground(Tk_Display(tkwin), stylePtr->gc, stylePtr->color->pixel); + return TRUE; + } + XSetForeground(Tk_Display(tkwin), stylePtr->gc, stylePtr->color->pixel); + Blt_DrawLayout(tkwin, drawable, stylePtr->gc, stylePtr->font, + Tk_Depth(tkwin), angle, x, y, layoutPtr, stylePtr->maxLength); + return TRUE; +} + +static void +Blt_DrawTextWithRotatedBitmap( + Tk_Window tkwin, + Drawable drawable, + float angle, + TextStyle *stylePtr, /* Text attribute information */ + TextLayout *layoutPtr, + int x, int y) /* Window coordinates to draw text */ +{ + int width, height; + Display *display; + Pixmap bitmap; + + display = Tk_Display(tkwin); + /* + * Rotate the text by writing the text into a bitmap and rotating the + * bitmap. Set the clip mask and origin in the GC first. And make sure + * we restore the GC because it may be shared. + */ + stylePtr->angle = angle; + + bitmap = Blt_Ts_Bitmap(tkwin, layoutPtr, stylePtr, &width, &height); + if (bitmap == None) { + return; + } + if ((bitmap != None) && (stylePtr->angle != 0.0)) { + Pixmap rotated; + + rotated = Blt_RotateBitmap(tkwin, bitmap, width, height, + stylePtr->angle, &width, &height); + Tk_FreePixmap(display, bitmap); + bitmap = rotated; + } + Blt_TranslateAnchor(x, y, width, height, stylePtr->anchor, &x, &y); + XSetClipMask(display, stylePtr->gc, bitmap); + + if (stylePtr->state & (STATE_DISABLED | STATE_EMPHASIS)) { + TkBorder *borderPtr = (TkBorder *) Blt_BackgroundBorder(stylePtr->bg); + XColor *color1, *color2; + + color1 = borderPtr->lightColor, color2 = borderPtr->darkColor; + if (stylePtr->state & STATE_EMPHASIS) { + XColor *hold; + + hold = color1, color1 = color2, color2 = hold; + } + if (color1 != NULL) { + XSetForeground(display, stylePtr->gc, color1->pixel); + } + XSetClipOrigin(display, stylePtr->gc, x + 1, y + 1); + XCopyPlane(display, bitmap, drawable, stylePtr->gc, 0, 0, width, + height, x + 1, y + 1, 1); + if (color2 != NULL) { + XSetForeground(display, stylePtr->gc, color2->pixel); + } + XSetClipOrigin(display, stylePtr->gc, x, y); + XCopyPlane(display, bitmap, drawable, stylePtr->gc, 0, 0, width, + height, x, y, 1); + XSetForeground(display, stylePtr->gc, stylePtr->color->pixel); + } else { + XSetForeground(display, stylePtr->gc, stylePtr->color->pixel); + XSetClipOrigin(display, stylePtr->gc, x, y); + XCopyPlane(display, bitmap, drawable, stylePtr->gc, 0, 0, width, height, + x, y, 1); + } + XSetClipMask(display, stylePtr->gc, None); + Tk_FreePixmap(display, bitmap); +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_Ts_DrawLayout -- + * + * Draw a text string, possibly rotated, using the the given window + * coordinates as an anchor for the text bounding box. If the text is + * not rotated, simply use the X text drawing routines. Otherwise, + * generate a bitmap of the rotated text. + * + * Results: + * Returns the x-coordinate to the right of the text. + * + * Side Effects: + * Text string is drawn using the given font and GC at the the given + * window coordinates. + * + * The Stipple, FillStyle, and TSOrigin fields of the GC are modified for + * rotated text. This assumes the GC is private, *not* shared (via + * Tk_GetGC) + * + *--------------------------------------------------------------------------- + */ +void +Blt_Ts_DrawLayout( + Tk_Window tkwin, + Drawable drawable, + TextLayout *layoutPtr, + TextStyle *stylePtr, /* Text attribute information */ + int x, int y) /* Window coordinates to draw text */ +{ + float angle; + + if ((stylePtr->gc == NULL) || (stylePtr->flags & UPDATE_GC)) { + Blt_Ts_ResetStyle(tkwin, stylePtr); + } + angle = (float)FMOD(stylePtr->angle, 360.0); + if (angle < 0.0) { + angle += 360.0; + } + if (angle == 0.0) { + /* + * This is the easy case of no rotation. Simply draw the text using + * the standard drawing routines. Handle offset printing for engraved + * (disabled) text. + */ + DrawStandardLayout(tkwin, drawable, stylePtr, layoutPtr, x, y); + return; + } + if (Blt_CanRotateFont(stylePtr->font, angle)) { + if (Blt_DrawTextWithRotatedFont(tkwin, drawable, angle, stylePtr, + layoutPtr, x, y)) { + return; /* Success. */ + } + } + /*Fallthru*/ + stylePtr->angle = (float)angle; + Blt_DrawTextWithRotatedBitmap(tkwin, drawable, angle, stylePtr, layoutPtr, + x, y); +} + +void +Blt_Ts_UnderlineLayout( + Tk_Window tkwin, + Drawable drawable, + TextLayout *layoutPtr, + TextStyle *stylePtr, /* Text attribute information */ + int x, int y) /* Window coordinates to draw text */ +{ + float angle; + + if ((stylePtr->gc == NULL) || (stylePtr->flags & UPDATE_GC)) { + Blt_Ts_ResetStyle(tkwin, stylePtr); + } + angle = (float)FMOD(stylePtr->angle, 360.0); + if (angle < 0.0) { + angle += 360.0; + } + if (angle == 0.0) { + TextFragment *fp, *fend; + + for (fp = layoutPtr->fragments, fend = fp + layoutPtr->nFrags; + fp < fend; fp++) { + int sx, sy; + + sx = x + fp->sx, sy = y + fp->sy; + Blt_UnderlineChars(Tk_Display(tkwin), drawable, stylePtr->gc, + stylePtr->font, fp->text, fp->count, sx, sy, 0, fp->count, + stylePtr->maxLength); + } + } +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_Ts_DrawLayout -- + * + * Draw a text string, possibly rotated, using the the given window + * coordinates as an anchor for the text bounding box. If the text is + * not rotated, simply use the X text drawing routines. Otherwise, + * generate a bitmap of the rotated text. + * + * Results: + * Returns the x-coordinate to the right of the text. + * + * Side Effects: + * Text string is drawn using the given font and GC at the the given + * window coordinates. + * + * The Stipple, FillStyle, and TSOrigin fields of the GC are modified for + * rotated text. This assumes the GC is private, *not* shared (via + * Tk_GetGC) + * + *--------------------------------------------------------------------------- + */ +void +Blt_Ts_DrawText( + Tk_Window tkwin, + Drawable drawable, + const char *text, + int textLen, + TextStyle *stylePtr, /* Text attribute information */ + int x, int y) /* Window coordinates to draw text */ +{ + TextLayout *layoutPtr; + + layoutPtr = Blt_Ts_CreateLayout(text, textLen, stylePtr); + Blt_Ts_DrawLayout(tkwin, drawable, layoutPtr, stylePtr, x, y); + Blt_Free(layoutPtr); +} + +void +Blt_DrawText2( + Tk_Window tkwin, + Drawable drawable, + const char *string, + TextStyle *stylePtr, /* Text attribute information */ + int x, int y, /* Window coordinates to draw text */ + Dim2D *areaPtr) +{ + TextLayout *layoutPtr; + int width, height; + float angle; + + if ((string == NULL) || (*string == '\0')) { + return; /* Empty string, do nothing */ + } + layoutPtr = Blt_Ts_CreateLayout(string, -1, stylePtr); + Blt_Ts_DrawLayout(tkwin, drawable, layoutPtr, stylePtr, x, y); + angle = FMOD(stylePtr->angle, 360.0); + if (angle < 0.0) { + angle += 360.0; + } + width = layoutPtr->width; + height = layoutPtr->height; + if (angle != 0.0) { + double rotWidth, rotHeight; + + Blt_GetBoundingBox(width, height, angle, &rotWidth, &rotHeight, + (Point2d *)NULL); + width = ROUND(rotWidth); + height = ROUND(rotHeight); + } + areaPtr->width = width; + areaPtr->height = height; + Blt_Free(layoutPtr); +} + +void +Blt_DrawText( + Tk_Window tkwin, + Drawable drawable, + const char *string, + TextStyle *stylePtr, /* Text attribute information */ + int x, int y) /* Window coordinates to draw text */ +{ + TextLayout *layoutPtr; + + if ((string == NULL) || (*string == '\0')) { + return; /* Empty string, do nothing */ + } + layoutPtr = Blt_Ts_CreateLayout(string, -1, stylePtr); + Blt_Ts_DrawLayout(tkwin, drawable, layoutPtr, stylePtr, x, y); + Blt_Free(layoutPtr); +} + +void +Blt_Ts_ResetStyle(Tk_Window tkwin, TextStyle *stylePtr) +{ + GC newGC; + XGCValues gcValues; + unsigned long gcMask; + + gcMask = GCFont; + gcValues.font = Blt_FontId(stylePtr->font); + if (stylePtr->color != NULL) { + gcMask |= GCForeground; + gcValues.foreground = stylePtr->color->pixel; + } + newGC = Tk_GetGC(tkwin, gcMask, &gcValues); + if (stylePtr->gc != NULL) { + Tk_FreeGC(Tk_Display(tkwin), stylePtr->gc); + } + stylePtr->gc = newGC; + stylePtr->flags &= ~UPDATE_GC; +} + +void +Blt_Ts_FreeStyle(Display *display, TextStyle *stylePtr) +{ + if (stylePtr->gc != NULL) { + Tk_FreeGC(display, stylePtr->gc); + } +} + +/* + * The following two structures are used to keep track of string + * measurement information when using the text layout facilities. + * + * A LayoutChunk represents a contiguous range of text that can be measured + * and displayed by low-level text calls. In general, chunks will be + * delimited by newlines and tabs. Low-level, platform-specific things + * like kerning and non-integer character widths may occur between the + * characters in a single chunk, but not between characters in different + * chunks. + * + * A TextLayout is a collection of LayoutChunks. It can be displayed with + * respect to any origin. It is the implementation of the Tk_TextLayout + * opaque token. + */ + +typedef struct LayoutChunk { + const char *start; /* Pointer to simple string to be displayed. + * This is a pointer into the TkTextLayout's + * string. */ + int numBytes; /* The number of bytes in this chunk. */ + int numChars; /* The number of characters in this chunk. */ + int numDisplayChars; /* The number of characters to display when + * this chunk is displayed. Can be less than + * numChars if extra space characters were + * absorbed by the end of the chunk. This + * will be < 0 if this is a chunk that is + * holding a tab or newline. */ + int x, y; /* The origin of the first character in this + * chunk with respect to the upper-left hand + * corner of the TextLayout. */ + int totalWidth; /* Width in pixels of this chunk. Used + * when hit testing the invisible spaces at + * the end of a chunk. */ + int displayWidth; /* Width in pixels of the displayable + * characters in this chunk. Can be less than + * width if extra space characters were + * absorbed by the end of the chunk. */ +} LayoutChunk; + +typedef struct TkTextLayout { + Blt_Font font; /* The font used when laying out the text. */ + const char *string; /* The string that was layed out. */ + int width; /* The maximum width of all lines in the + * text layout. */ + int numChunks; /* Number of chunks actually used in + * following array. */ + LayoutChunk chunks[1]; /* Array of chunks. The actual size will + * be maxChunks. THIS FIELD MUST BE THE LAST + * IN THE STRUCTURE. */ +} TkTextLayout; + + +/* + *--------------------------------------------------------------------------- + * + * Blt_FreeTextLayout -- + * + * This procedure is called to release the storage associated with + * a Tk_TextLayout when it is no longer needed. + * + * Results: + * None. + * + * Side effects: + * Memory is freed. + * + *--------------------------------------------------------------------------- + */ + +void +Blt_FreeTextLayout(Tk_TextLayout textLayout) +{ + TkTextLayout *layoutPtr = (TkTextLayout *) textLayout; + + if (layoutPtr != NULL) { + Blt_Free(layoutPtr); + } +} + +/* + *--------------------------------------------------------------------------- + * + * NewChunk -- + * + * Helper function for Blt_ComputeTextLayout(). Encapsulates a + * measured set of characters in a chunk that can be quickly + * drawn. + * + * Results: + * A pointer to the new chunk in the text layout. + * + * Side effects: + * The text layout is reallocated to hold more chunks as necessary. + * + * Currently, Tk_ComputeTextLayout() stores contiguous ranges of + * "normal" characters in a chunk, along with individual tab + * and newline chars in their own chunks. All characters in the + * text layout are accounted for. + * + *--------------------------------------------------------------------------- + */ +static LayoutChunk * +NewChunk(TkTextLayout **layoutPtrPtr, int *maxPtr, const char *start, + int numBytes, int curX, int newX, int y) +{ + TkTextLayout *layoutPtr; + LayoutChunk *chunkPtr; + int maxChunks, numChars; + size_t s; + + layoutPtr = *layoutPtrPtr; + maxChunks = *maxPtr; + if (layoutPtr->numChunks == maxChunks) { + maxChunks *= 2; + s = sizeof(TkTextLayout) + ((maxChunks - 1) * sizeof(LayoutChunk)); + layoutPtr = Blt_Realloc(layoutPtr, s); + *layoutPtrPtr = layoutPtr; + *maxPtr = maxChunks; + } + numChars = Tcl_NumUtfChars(start, numBytes); + chunkPtr = &layoutPtr->chunks[layoutPtr->numChunks]; + chunkPtr->start = start; + chunkPtr->numBytes = numBytes; + chunkPtr->numChars = numChars; + chunkPtr->numDisplayChars = numChars; + chunkPtr->x = curX; + chunkPtr->y = y; + chunkPtr->totalWidth = newX - curX; + chunkPtr->displayWidth = newX - curX; + layoutPtr->numChunks++; + + return chunkPtr; +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_ComputeTextLayout -- + * + * Computes the amount of screen space needed to display a multi-line, + * justified string of text. Records all the measurements that were done + * to determine to size and positioning of the individual lines of text; + * this information can be used by the Tk_DrawTextLayout() procedure to + * display the text quickly (without remeasuring it). + * + * This procedure is useful for simple widgets that want to display + * single-font, multi-line text and want Tk to handle the details. + * + * Results: + * The return value is a Tk_TextLayout token that holds the measurement + * information for the given string. The token is only valid for the + * given string. If the string is freed, the token is no longer valid + * and must also be freed. To free the token, call Tk_FreeTextLayout(). + * + * The dimensions of the screen area needed to display the text are + * stored in *widthPtr and *heightPtr. + * + * Side effects: + * Memory is allocated to hold the measurement information. + * + *--------------------------------------------------------------------------- + */ + +Tk_TextLayout +Blt_ComputeTextLayout( + Blt_Font font, /* Font that will be used to display text. */ + const char *string, /* String whose dimensions are to be + * computed. */ + int numChars, /* Number of characters to consider from + * string, or < 0 for strlen(). */ + int wrapLength, /* Longest permissible line length, in + * pixels. <= 0 means no automatic wrapping: + * just let lines get as long as needed. */ + Tk_Justify justify, /* How to justify lines. */ + int flags, /* Flag bits OR-ed together. + * TK_IGNORE_TABS means that tab characters + * should not be expanded. TK_IGNORE_NEWLINES + * means that newline characters should not + * cause a line break. */ + int *widthPtr, /* Filled with width of string. */ + int *heightPtr) /* Filled with height of string. */ +{ + const char *start, *end, *special; + int n, y, bytesThisChunk, maxChunks; + int baseline, height, curX, newX, maxWidth; + TkTextLayout *layoutPtr; + LayoutChunk *chunkPtr; + Blt_FontMetrics fm; + Tcl_DString lineBuffer; + int *lineLengths; + int curLine, layoutHeight; + + Tcl_DStringInit(&lineBuffer); + + if ((font == NULL) || (string == NULL)) { + if (widthPtr != NULL) { + *widthPtr = 0; + } + if (heightPtr != NULL) { + *heightPtr = 0; + } + return NULL; + } + + Blt_GetFontMetrics(font, &fm); + height = fm.ascent + fm.descent; + + if (numChars < 0) { + numChars = Tcl_NumUtfChars(string, -1); + } + if (wrapLength == 0) { + wrapLength = -1; + } + + maxChunks = 1; + + layoutPtr = Blt_AssertMalloc(sizeof(TkTextLayout) + (maxChunks - 1) * + sizeof(LayoutChunk)); + layoutPtr->font = font; + layoutPtr->string = string; + layoutPtr->numChunks = 0; + + baseline = fm.ascent; + maxWidth = 0; + + /* + * Divide the string up into simple strings and measure each string. + */ + + curX = 0; + + end = Tcl_UtfAtIndex(string, numChars); + special = string; + + flags &= TK_IGNORE_TABS | TK_IGNORE_NEWLINES; + flags |= TK_WHOLE_WORDS | TK_AT_LEAST_ONE; + for (start = string; start < end; ) { + if (start >= special) { + /* + * Find the next special character in the string. + * + * INTL: Note that it is safe to increment by byte, because we are + * looking for 7-bit characters that will appear unchanged in + * UTF-8. At some point we may need to support the full Unicode + * whitespace set. + */ + + for (special = start; special < end; special++) { + if (!(flags & TK_IGNORE_NEWLINES)) { + if ((*special == '\n') || (*special == '\r')) { + break; + } + } + if (!(flags & TK_IGNORE_TABS)) { + if (*special == '\t') { + break; + } + } + } + } + + /* + * Special points at the next special character (or the end of the + * string). Process characters between start and special. + */ + + chunkPtr = NULL; + if (start < special) { + bytesThisChunk = Blt_MeasureChars(font, start, special - start, + wrapLength - curX, flags, &newX); + newX += curX; + flags &= ~TK_AT_LEAST_ONE; + if (bytesThisChunk > 0) { + chunkPtr = NewChunk(&layoutPtr, &maxChunks, start, + bytesThisChunk, curX, newX, baseline); + + start += bytesThisChunk; + curX = newX; + } + } + + if ((start == special) && (special < end)) { + /* + * Handle the special character. + * + * INTL: Special will be pointing at a 7-bit character so we + * can safely treat it as a single byte. + */ + + chunkPtr = NULL; + if (*special == '\t') { + newX = curX + fm.tabWidth; + newX -= newX % fm.tabWidth; + NewChunk(&layoutPtr, &maxChunks, start, 1, curX, newX, + baseline)->numDisplayChars = -1; + start++; + if ((start < end) && + ((wrapLength <= 0) || (newX <= wrapLength))) { + /* + * More chars can still fit on this line. + */ + + curX = newX; + flags &= ~TK_AT_LEAST_ONE; + continue; + } + } else { + NewChunk(&layoutPtr, &maxChunks, start, 1, curX, curX, + baseline)->numDisplayChars = -1; + start++; + goto wrapLine; + } + } + + /* + * No more characters are going to go on this line, either because + * no more characters can fit or there are no more characters left. + * Consume all extra spaces at end of line. + */ + + while ((start < end) && isspace(UCHAR(*start))) { /* INTL: ISO space */ + if (!(flags & TK_IGNORE_NEWLINES)) { + if ((*start == '\n') || (*start == '\r')) { + break; + } + } + if (!(flags & TK_IGNORE_TABS)) { + if (*start == '\t') { + break; + } + } + start++; + } + if (chunkPtr != NULL) { + const char *end; + + /* + * Append all the extra spaces on this line to the end of the + * last text chunk. This is a little tricky because we are + * switching back and forth between characters and bytes. + */ + + end = chunkPtr->start + chunkPtr->numBytes; + bytesThisChunk = start - end; + if (bytesThisChunk > 0) { + bytesThisChunk = Blt_MeasureChars(font, end, bytesThisChunk, + -1, 0, &chunkPtr->totalWidth); + chunkPtr->numBytes += bytesThisChunk; + chunkPtr->numChars += Tcl_NumUtfChars(end, bytesThisChunk); + chunkPtr->totalWidth += curX; + } + } + + wrapLine: + flags |= TK_AT_LEAST_ONE; + + /* + * Save current line length, then move current position to start of + * next line. + */ + + if (curX > maxWidth) { + maxWidth = curX; + } + + /* + * Remember width of this line, so that all chunks on this line + * can be centered or right justified, if necessary. + */ + + Tcl_DStringAppend(&lineBuffer, (char *) &curX, sizeof(curX)); + + curX = 0; + baseline += height; + } + + /* + * If last line ends with a newline, then we need to make a 0 width + * chunk on the next line. Otherwise "Hello" and "Hello\n" are the + * same height. + */ + + if ((layoutPtr->numChunks > 0) && ((flags & TK_IGNORE_NEWLINES) == 0)) { + if (layoutPtr->chunks[layoutPtr->numChunks - 1].start[0] == '\n') { + chunkPtr = NewChunk(&layoutPtr, &maxChunks, start, 0, curX, + curX, baseline); + chunkPtr->numDisplayChars = -1; + Tcl_DStringAppend(&lineBuffer, (char *) &curX, sizeof(curX)); + baseline += height; + } + } + + layoutPtr->width = maxWidth; + layoutHeight = baseline - fm.ascent; + if (layoutPtr->numChunks == 0) { + layoutHeight = height; + + /* + * This fake chunk is used by the other procedures so that they can + * pretend that there is a chunk with no chars in it, which makes + * the coding simpler. + */ + + layoutPtr->numChunks = 1; + layoutPtr->chunks[0].start = string; + layoutPtr->chunks[0].numBytes = 0; + layoutPtr->chunks[0].numChars = 0; + layoutPtr->chunks[0].numDisplayChars = -1; + layoutPtr->chunks[0].x = 0; + layoutPtr->chunks[0].y = fm.ascent; + layoutPtr->chunks[0].totalWidth = 0; + layoutPtr->chunks[0].displayWidth = 0; + } else { + /* + * Using maximum line length, shift all the chunks so that the lines + * are all justified correctly. + */ + + curLine = 0; + chunkPtr = layoutPtr->chunks; + y = chunkPtr->y; + lineLengths = (int *) Tcl_DStringValue(&lineBuffer); + for (n = 0; n < layoutPtr->numChunks; n++) { + int extra; + + if (chunkPtr->y != y) { + curLine++; + y = chunkPtr->y; + } + extra = maxWidth - lineLengths[curLine]; + if (justify == TK_JUSTIFY_CENTER) { + chunkPtr->x += extra / 2; + } else if (justify == TK_JUSTIFY_RIGHT) { + chunkPtr->x += extra; + } + chunkPtr++; + } + } + + if (widthPtr != NULL) { + *widthPtr = layoutPtr->width; + } + if (heightPtr != NULL) { + *heightPtr = layoutHeight; + } + Tcl_DStringFree(&lineBuffer); + + return (Tk_TextLayout) layoutPtr; +} + +void +Blt_DrawTextLayout( + Display *display, /* Display on which to draw. */ + Drawable drawable, /* Window or pixmap in which to draw. */ + GC gc, /* Graphics context to use for drawing text. */ + Tk_TextLayout layout, /* Layout information, from a previous call + * to Blt_ComputeTextLayout(). */ + int x, int y, /* Upper-left hand corner of rectangle in + * which to draw (pixels). */ + int firstChar, /* The index of the first character to draw + * from the given text item. 0 specfies the + * beginning. */ + int lastChar) /* The index just after the last character + * to draw from the given text item. A number + * < 0 means to draw all characters. */ +{ + TkTextLayout *layoutPtr; + int i, numDisplayChars, drawX; + const char *firstByte; + const char *lastByte; + LayoutChunk *chunkPtr; + int depth = 24; + + layoutPtr = (TkTextLayout *) layout; + if (layoutPtr == NULL) { + return; + } + + if (lastChar < 0) { + lastChar = 100000000; + } + chunkPtr = layoutPtr->chunks; + for (i = 0; i < layoutPtr->numChunks; i++) { + numDisplayChars = chunkPtr->numDisplayChars; + if ((numDisplayChars > 0) && (firstChar < numDisplayChars)) { + if (firstChar <= 0) { + drawX = 0; + firstChar = 0; + firstByte = chunkPtr->start; + } else { + firstByte = Tcl_UtfAtIndex(chunkPtr->start, firstChar); + Blt_MeasureChars(layoutPtr->font, chunkPtr->start, + firstByte - chunkPtr->start, -1, 0, &drawX); + } + if (lastChar < numDisplayChars) { + numDisplayChars = lastChar; + } + lastByte = Tcl_UtfAtIndex(chunkPtr->start, numDisplayChars); + Blt_DrawChars(display, drawable, gc, layoutPtr->font, depth, 0.0f, + firstByte, lastByte - firstByte, + x + chunkPtr->x + drawX, y + chunkPtr->y); + } + firstChar -= chunkPtr->numChars; + lastChar -= chunkPtr->numChars; + if (lastChar <= 0) { + break; + } + chunkPtr++; + } +} + +/* + *--------------------------------------------------------------------------- + * + * Tk_CharBbox -- + * + * Use the information in the Tk_TextLayout token to return the + * bounding box for the character specified by index. + * + * The width of the bounding box is the advance width of the + * character, and does not include and left- or right-bearing. + * Any character that extends partially outside of the + * text layout is considered to be truncated at the edge. Any + * character which is located completely outside of the text + * layout is considered to be zero-width and pegged against + * the edge. + * + * The height of the bounding box is the line height for this font, + * extending from the top of the ascent to the bottom of the + * descent. Information about the actual height of the individual + * letter is not available. + * + * A text layout that contains no characters is considered to + * contain a single zero-width placeholder character. + * + * Results: + * The return value is 0 if the index did not specify a character + * in the text layout, or non-zero otherwise. In that case, + * *bbox is filled with the bounding box of the character. + * + * Side effects: + * None. + * + *--------------------------------------------------------------------------- + */ + +int +Blt_CharBbox( + Tk_TextLayout layout, /* Layout information, from a previous call to + * Tk_ComputeTextLayout(). */ + int index, /* The index of the character whose bbox is + * desired. */ + int *xPtr, int *yPtr, /* Filled with the upper-left hand corner, in + * pixels, of the bounding box for the character + * specified by index, if non-NULL. */ + int *widthPtr, + int *heightPtr) /* Filled with the width and height of the + * bounding box for the character specified by + * index, if non-NULL. */ +{ + TkTextLayout *layoutPtr; + LayoutChunk *chunkPtr; + int i, x, w; + Blt_Font font; + const char *end; + Blt_FontMetrics fm; + + if (index < 0) { + return 0; + } + + layoutPtr = (TkTextLayout *) layout; + chunkPtr = layoutPtr->chunks; + font = layoutPtr->font; + + Blt_GetFontMetrics(font, &fm); + for (i = 0; i < layoutPtr->numChunks; i++) { + if (chunkPtr->numDisplayChars < 0) { + if (index == 0) { + x = chunkPtr->x; + w = chunkPtr->totalWidth; + goto check; + } + } else if (index < chunkPtr->numChars) { + end = Tcl_UtfAtIndex(chunkPtr->start, index); + if (xPtr != NULL) { + Blt_MeasureChars(font, chunkPtr->start, + end - chunkPtr->start, -1, 0, &x); + x += chunkPtr->x; + } + if (widthPtr != NULL) { + Blt_MeasureChars(font, end, Tcl_UtfNext(end) - end, -1, 0, &w); + } + goto check; + } + index -= chunkPtr->numChars; + chunkPtr++; + } + if (index == 0) { + /* + * Special case to get location just past last char in layout. + */ + + chunkPtr--; + x = chunkPtr->x + chunkPtr->totalWidth; + w = 0; + } else { + return 0; + } + + /* + * Ensure that the bbox lies within the text layout. This forces all + * chars that extend off the right edge of the text layout to have + * truncated widths, and all chars that are completely off the right + * edge of the text layout to peg to the edge and have 0 width. + */ + check: + if (yPtr != NULL) { + *yPtr = chunkPtr->y - fm.ascent; + } + if (heightPtr != NULL) { + *heightPtr = fm.ascent + fm.descent; + } + + if (x > layoutPtr->width) { + x = layoutPtr->width; + } + if (xPtr != NULL) { + *xPtr = x; + } + if (widthPtr != NULL) { + if (x + w > layoutPtr->width) { + w = layoutPtr->width - x; + } + *widthPtr = w; + } + + return 1; +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_UnderlineTextLayout -- + * + * Use the information in the Tk_TextLayout token to display an + * underline below an individual character. This procedure does + * not draw the text, just the underline. + * + * This procedure is useful for simple widgets that need to + * display single-font, multi-line text with an individual + * character underlined and want Tk to handle the details. + * To display larger amounts of underlined text, construct + * and use an underlined font. + * + * Results: + * None. + * + * Side effects: + * Underline drawn on the screen. + * + *--------------------------------------------------------------------------- + */ + +void +Blt_UnderlineTextLayout( + Display *display, /* Display on which to draw. */ + Drawable drawable, /* Window or pixmap in which to draw. */ + GC gc, /* Graphics context to use for drawing text. */ + Tk_TextLayout layout, /* Layout information, from a previous call + * to Tk_ComputeTextLayout(). */ + int x, int y, /* Upper-left hand corner of rectangle in + * which to draw (pixels). */ + int underline) /* Index of the single character to + * underline, or -1 for no underline. */ +{ + TkTextLayout *layoutPtr; + int xx, yy, width, height; + + if ((Blt_CharBbox(layout, underline, &xx, &yy, &width, &height) != 0) + && (width != 0)) { + Blt_FontMetrics fm; + layoutPtr = (TkTextLayout *) layout; + Blt_GetFontMetrics(layoutPtr->font, &fm); + XFillRectangle(display, drawable, gc, x + xx, + y + yy + fm.ascent + fm.underlinePos, + (unsigned int) width, fm.underlineHeight); + } +} diff --git a/src/bltGrText.h b/src/bltGrText.h new file mode 100644 index 0000000..7d4ef3d --- /dev/null +++ b/src/bltGrText.h @@ -0,0 +1,265 @@ + +/* + * bltText.h -- + * + * + * Copyright 1993-2004 George A Howlett. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef _BLT_TEXT_H +#define _BLT_TEXT_H + +#include "bltBgStyle.h" + +#define DEF_TEXT_FLAGS (TK_PARTIAL_OK | TK_IGNORE_NEWLINES) +#define UPDATE_GC 1 + +/* + * TextFragment -- + */ +typedef struct { + const char *text; /* Text string to be displayed */ + + size_t count; /* Number of bytes in text. The actual + * character count may differ because of + * multi-byte UTF encodings. */ + + short x, y; /* X-Y offset of the baseline from the + * upper-left corner of the bbox. */ + + short sx, sy; /* Starting offset of text using rotated + * font. */ + + int width; /* Width of segment in pixels. This + * information is used to draw + * PostScript strings the same width + * as X. */ +} TextFragment; + + +/* + * TextItem -- + * + * Parsed form for markup string. Each item is a scrap of text + * describes the font, position, and characters to be displayed. + * + * subscript x_y very small subset of latex markup. + * superscript x^y + * grouping a^{x+y} a_{i,j} + * supersuper a^{10^8} + * \hat{a} \bar{b} \vec{c} + * \overline{} \underline{} + * \frac \tfrac + * \Alpha \Beta ... + * \mathbf{} \mathit{} \mathrm{} \boldsymbol{} + * \angstrom \degree + * + * -mathtext instead of -text + * + * Can use TextItem where you don't directly edit the text: + * label, treeview, graph, barchart... + * + * Font selector (bold, italic, size adjust) from base font. + * Global font table reference counted. + * + */ +typedef struct { + const char *text; /* Text string to be displayed */ + + size_t count; /* Number of bytes in text. The actual + * character count may differ because of + * multi-byte UTF encodings. */ + + short int x, y; /* X-Y offset of the baseline from the + * upper-left corner of the bbox. */ + + short int sx, sy; /* Starting offset of text using rotated + * font. */ + + Blt_Font font; /* Allocated font for this chunk. + * If NULL, use the global font. */ + + int underline; /* Text is underlined */ + + int width; /* Width of segment in pixels. This + * information is used to draw + * PostScript strings the same width + * as X. (deprecated) */ +} TextItem; + +/* + * TextLayout -- + */ +typedef struct { + TextFragment *underlinePtr; + int underline; + size_t width, height; /* Dimensions of text bounding box */ + size_t nFrags; /* # fragments of text */ + TextFragment fragments[1]; /* Information about each fragment of text */ +} TextLayout; + +/* + * TextStyle -- + * + * A somewhat convenient structure to hold text attributes that determine + * how a text string is to be displayed on the screen or drawn with + * PostScript commands. The alternative is to pass lots of parameters to + * the drawing and printing routines. This seems like a more efficient + * and less cumbersome way of passing parameters. + */ +typedef struct { + unsigned int state; /* If non-zero, indicates to draw text + * in the active color */ + XColor *color; /* Color to draw the text. */ + Blt_Font font; /* Font to use to draw text */ + Blt_Background bg; /* Background color of text. This is + * also used for drawing disabled + * text. */ + float angle; /* Rotation of text in degrees. */ + Tk_Justify justify; /* Justification of the text + * string. This only matters if the + * text is composed of multiple + * lines. */ + Tk_Anchor anchor; /* Indicates how the text is anchored + * around its x,y coordinates. */ + Blt_Pad xPad, yPad; /* # pixels padding of around text + * region. */ + unsigned short int leader; /* # pixels spacing between lines of + * text. */ + short int underline; /* Index of character to be underlined, + * -1 if no underline. */ + int maxLength; /* Maximum length in pixels of text */ + /* Private fields. */ + unsigned short flags; + GC gc; /* GC used to draw the text */ +} TextStyle; + +BLT_EXTERN TextLayout *Blt_Ts_CreateLayout(const char *string, int length, + TextStyle *tsPtr); + +BLT_EXTERN void Blt_Ts_DrawLayout(Tk_Window tkwin, Drawable drawable, + TextLayout *textPtr, TextStyle *tsPtr, int x, int y); + +BLT_EXTERN void Blt_Ts_GetExtents(TextStyle *tsPtr, const char *text, + unsigned int *widthPtr, unsigned int *heightPtr); + +BLT_EXTERN void Blt_Ts_ResetStyle(Tk_Window tkwin, TextStyle *tsPtr); + +BLT_EXTERN void Blt_Ts_FreeStyle(Display *display, TextStyle *tsPtr); + +BLT_EXTERN void Blt_Ts_SetDrawStyle (TextStyle *tsPtr, Blt_Font font, GC gc, + XColor *fgColor, float angle, Tk_Anchor anchor, Tk_Justify justify, + int leader); + +BLT_EXTERN void Blt_Ts_SetPrintStyle(TextStyle *tsPtr, Blt_Font font, + XColor *fgColor, XColor *bgColor, float angle, Tk_Anchor anchor, + Tk_Justify justify, int leader); + +BLT_EXTERN void Blt_DrawText(Tk_Window tkwin, Drawable drawable, + const char *string, TextStyle *tsPtr, int x, int y); + +BLT_EXTERN void Blt_DrawText2(Tk_Window tkwin, Drawable drawable, + const char *string, TextStyle *tsPtr, int x, int y, Dim2D * dimPtr); + +BLT_EXTERN Pixmap Blt_Ts_Bitmap(Tk_Window tkwin, TextLayout *textPtr, + TextStyle *tsPtr, int *widthPtr, int *heightPtr); + +BLT_EXTERN int Blt_DrawTextWithRotatedFont(Tk_Window tkwin, Drawable drawable, + float angle, TextStyle *tsPtr, TextLayout *textPtr, int x, int y); + +BLT_EXTERN void Blt_DrawLayout(Tk_Window tkwin, Drawable drawable, GC gc, + Blt_Font font, int depth, float angle, int x, int y, + TextLayout *layoutPtr, int maxLength); + +BLT_EXTERN void Blt_GetTextExtents(Blt_Font font, int leader, const char *text, + int textLen, unsigned int *widthPtr, unsigned int *heightPtr); + +BLT_EXTERN void Blt_RotateStartingTextPositions(TextLayout *textPtr, + float angle); + +BLT_EXTERN Tk_TextLayout Blt_ComputeTextLayout(Blt_Font font, + const char *string, int numChars, int wrapLength, Tk_Justify justify, + int flags, int *widthPtr, int *heightPtr); + +BLT_EXTERN void Blt_DrawTextLayout(Display *display, Drawable drawable, GC gc, + Tk_TextLayout layout, int x, int y, int firstChar, int lastChar); + +BLT_EXTERN int Blt_CharBbox(Tk_TextLayout layout, int index, int *xPtr, + int *yPtr, int *widthPtr, int *heightPtr); + +BLT_EXTERN void Blt_UnderlineTextLayout(Display *display, Drawable drawable, + GC gc, Tk_TextLayout layout, int x, int y, int underline); + +BLT_EXTERN void Blt_Ts_UnderlineLayout(Tk_Window tkwin, Drawable drawable, + TextLayout *layoutPtr, TextStyle *tsPtr, int x, int y); + +BLT_EXTERN void Blt_Ts_DrawText(Tk_Window tkwin, Drawable drawable, + const char *text, int textLen, TextStyle *tsPtr, int x, int y); + +BLT_EXTERN int Blt_MeasureText(Blt_Font font, const char *text, int textLen, + int maxLength, int *nBytesPtr); + +BLT_EXTERN void Blt_FreeTextLayout(Tk_TextLayout layout); + +#define Blt_Ts_GetAnchor(ts) ((ts).anchor) +#define Blt_Ts_GetAngle(ts) ((ts).angle) +#define Blt_Ts_GetBackground(ts) ((ts).bg) +#define Blt_Ts_GetFont(ts) ((ts).font) +#define Blt_Ts_GetForeground(ts) ((ts).color) +#define Blt_Ts_GetJustify(ts) ((ts).justify) +#define Blt_Ts_GetLeader(ts) ((ts).leader) + +#define Blt_Ts_SetAnchor(ts, a) ((ts).anchor = (a)) +#define Blt_Ts_SetAngle(ts, r) ((ts).angle = (float)(r)) +#define Blt_Ts_SetBackground(ts, b) ((ts).bg = (b)) +#define Blt_Ts_SetFont(ts, f) \ + (((ts).font != (f)) ? ((ts).font = (f), (ts).flags |= UPDATE_GC) : 0) +#define Blt_Ts_SetForeground(ts, c) \ + (((ts).color != (c)) ? ((ts).color = (c), (ts).flags |= UPDATE_GC) : 0) +#define Blt_Ts_SetGC(ts, g) ((ts).gc = (g)) +#define Blt_Ts_SetJustify(ts, j) ((ts).justify = (j)) +#define Blt_Ts_SetLeader(ts, l) ((ts).leader = (l)) +#define Blt_Ts_SetMaxLength(ts, l) ((ts).maxLength = (l)) +#define Blt_Ts_SetPadding(ts, l, r, t, b) \ + ((ts).xPad.side1 = (l), \ + (ts).xPad.side2 = (r), \ + (ts).yPad.side1 = (t), \ + (ts).yPad.side2 = (b)) +#define Blt_Ts_SetState(ts, s) ((ts).state = (s)) +#define Blt_Ts_SetUnderline(ts, ul) ((ts).underline = (ul)) + +#define Blt_Ts_InitStyle(ts) \ + ((ts).anchor = TK_ANCHOR_NW, \ + (ts).color = (XColor *)NULL, \ + (ts).font = NULL, \ + (ts).justify = TK_JUSTIFY_LEFT, \ + (ts).leader = 0, \ + (ts).underline = -1, \ + (ts).xPad.side1 = (ts).xPad.side2 = 0, \ + (ts).yPad.side1 = (ts).yPad.side2 = 0, \ + (ts).state = 0, \ + (ts).flags = 0, \ + (ts).gc = NULL, \ + (ts).maxLength = -1, \ + (ts).angle = 0.0) + +#endif /* _BLT_TEXT_H */ diff --git a/src/bltGraph.C b/src/bltGraph.C new file mode 100644 index 0000000..de768f1 --- /dev/null +++ b/src/bltGraph.C @@ -0,0 +1,2381 @@ + +/* + * bltGraph.c -- + * + * This module implements a graph widget for the BLT toolkit. + * + * The graph widget was created by Sani Nassif and George Howlett. + * + * Copyright 1991-2004 George A Howlett. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* + * To do: + * + * 2) Update manual pages. + * + * 3) Update comments. + * + * 5) Surface, contour, and flow graphs + * + * 7) Arrows for line markers + * + */ + +#include "bltGraph.h" +#include "bltOp.h" +#include "bltBind.h" +#include "bltGrElem.h" +#include "bltGrLegd.h" +#include "bltSwitch.h" +#include <X11/Xutil.h> +#include "tkDisplay.h" +#include "bltPicture.h" + +typedef int (GraphCmdProc)(Graph *graphPtr, Tcl_Interp *interp, int objc, + Tcl_Obj *const *objv); + +/* + * Objects in the graph have their own class names. These class names are + * used for the resource database and bindings. Example. + * + * option add *X.title "X Axis Title" widgetDefault + * .g marker bind BitmapMarker <Enter> { ... } + * + * The option database trick is performed by creating a temporary window when + * an object is initially configured. The class name of the temporary window + * will be from the list below. + */ +static const char *objectClassNames[] = { + "unknown", + "XAxis", + "YAxis", + "BarElement", + "ContourElement", + "LineElement", + "StripElement", + "BitmapMarker", + "ImageMarker", + "LineMarker", + "PolygonMarker", + "TextMarker", + "WindowMarker", +}; + +BLT_EXTERN Blt_CustomOption bltLinePenOption; +BLT_EXTERN Blt_CustomOption bltBarPenOption; +BLT_EXTERN Blt_CustomOption bltBarModeOption; + +#define DEF_GRAPH_ASPECT_RATIO "0.0" +#define DEF_GRAPH_BAR_BASELINE "0.0" +#define DEF_GRAPH_BAR_MODE "normal" +#define DEF_GRAPH_BAR_WIDTH "0.9" +#define DEF_GRAPH_BACKGROUND STD_NORMAL_BACKGROUND +#define DEF_GRAPH_BORDERWIDTH STD_BORDERWIDTH +#define DEF_GRAPH_BUFFER_ELEMENTS "yes" +#define DEF_GRAPH_BUFFER_GRAPH "1" +#define DEF_GRAPH_CURSOR "crosshair" +#define DEF_GRAPH_FONT "{Sans Serif} 12" +#define DEF_GRAPH_HALO "2m" +#define DEF_GRAPH_HALO_BAR "0.1i" +#define DEF_GRAPH_HEIGHT "4i" +#define DEF_GRAPH_HIGHLIGHT_BACKGROUND STD_NORMAL_BACKGROUND +#define DEF_GRAPH_HIGHLIGHT_COLOR RGB_BLACK +#define DEF_GRAPH_HIGHLIGHT_WIDTH "2" +#define DEF_GRAPH_INVERT_XY "0" +#define DEF_GRAPH_JUSTIFY "center" +#define DEF_GRAPH_MARGIN "0" +#define DEF_GRAPH_MARGIN_VAR (char *)NULL +#define DEF_GRAPH_PLOT_BACKGROUND RGB_WHITE +#define DEF_GRAPH_PLOT_BORDERWIDTH "1" +#define DEF_GRAPH_PLOT_PADX "0" +#define DEF_GRAPH_PLOT_PADY "0" +#define DEF_GRAPH_PLOT_RELIEF "solid" +#define DEF_GRAPH_RELIEF "flat" +#define DEF_GRAPH_SHOW_VALUES "no" +#define DEF_GRAPH_STACK_AXES "no" +#define DEF_GRAPH_TAKE_FOCUS "" +#define DEF_GRAPH_TITLE (char *)NULL +#define DEF_GRAPH_TITLE_COLOR STD_NORMAL_FOREGROUND +#define DEF_GRAPH_WIDTH "5i" +#define DEF_GRAPH_DATA (char *)NULL +#define DEF_GRAPH_DATA_COMMAND (char *)NULL +#define DEF_GRAPH_UNMAP_HIDDEN_ELEMENTS "0" + +static Blt_ConfigSpec configSpecs[] = +{ + {BLT_CONFIG_FLOAT, "-aspect", "aspect", "Aspect", DEF_GRAPH_ASPECT_RATIO, + Blt_Offset(Graph, aspect), BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_BACKGROUND, "-background", "background", "Background", + DEF_GRAPH_BACKGROUND, Blt_Offset(Graph, normalBg), 0}, + {BLT_CONFIG_CUSTOM, "-barmode", "barMode", "BarMode", DEF_GRAPH_BAR_MODE, + Blt_Offset(Graph, mode), BLT_CONFIG_DONT_SET_DEFAULT, + &bltBarModeOption}, + {BLT_CONFIG_FLOAT, "-barwidth", "barWidth", "BarWidth", + DEF_GRAPH_BAR_WIDTH, Blt_Offset(Graph, barWidth), 0}, + {BLT_CONFIG_FLOAT, "-baseline", "baseline", "Baseline", + DEF_GRAPH_BAR_BASELINE, Blt_Offset(Graph, baseline), 0}, + {BLT_CONFIG_SYNONYM, "-bd", "borderWidth", (char *)NULL, (char *)NULL,0, 0}, + {BLT_CONFIG_SYNONYM, "-bg", "background", (char *)NULL, (char *)NULL, 0, 0}, + {BLT_CONFIG_SYNONYM, "-bm", "bottomMargin", (char *)NULL, (char *)NULL, + 0, 0}, + {BLT_CONFIG_PIXELS_NNEG, "-borderwidth", "borderWidth", "BorderWidth", + DEF_GRAPH_BORDERWIDTH, Blt_Offset(Graph, borderWidth), + BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_PIXELS_NNEG, "-bottommargin", "bottomMargin", "Margin", + DEF_GRAPH_MARGIN, Blt_Offset(Graph, bottomMargin.reqSize), 0}, + {BLT_CONFIG_STRING, "-bottomvariable", "bottomVariable", "BottomVariable", + DEF_GRAPH_MARGIN_VAR, Blt_Offset(Graph, bottomMargin.varName), + BLT_CONFIG_NULL_OK}, + {BLT_CONFIG_BOOLEAN, "-bufferelements", "bufferElements", "BufferElements", + DEF_GRAPH_BUFFER_ELEMENTS, Blt_Offset(Graph, backingStore), + BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_BOOLEAN, "-buffergraph", "bufferGraph", "BufferGraph", + DEF_GRAPH_BUFFER_GRAPH, Blt_Offset(Graph, doubleBuffer), + BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_ACTIVE_CURSOR, "-cursor", "cursor", "Cursor", + DEF_GRAPH_CURSOR, Blt_Offset(Graph, cursor), BLT_CONFIG_NULL_OK}, + {BLT_CONFIG_STRING, "-data", "data", "Data", + (char *)NULL, Blt_Offset(Graph, data), BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_STRING, "-datacommand", "dataCommand", "DataCommand", + (char *)NULL, Blt_Offset(Graph, dataCmd), BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_SYNONYM, "-fg", "foreground", (char *)NULL, (char *)NULL, 0, 0}, + {BLT_CONFIG_FONT, "-font", "font", "Font", + DEF_GRAPH_FONT, Blt_Offset(Graph, titleTextStyle.font), 0}, + {BLT_CONFIG_COLOR, "-foreground", "foreground", "Foreground", + DEF_GRAPH_TITLE_COLOR, Blt_Offset(Graph, titleTextStyle.color), 0}, + {BLT_CONFIG_PIXELS_NNEG, "-halo", "halo", "Halo", DEF_GRAPH_HALO, + Blt_Offset(Graph, halo), 0}, + {BLT_CONFIG_PIXELS_NNEG, "-height", "height", "Height", DEF_GRAPH_HEIGHT, + Blt_Offset(Graph, reqHeight), 0}, + {BLT_CONFIG_COLOR, "-highlightbackground", "highlightBackground", + "HighlightBackground", DEF_GRAPH_HIGHLIGHT_BACKGROUND, + Blt_Offset(Graph, highlightBgColor), 0}, + {BLT_CONFIG_COLOR, "-highlightcolor", "highlightColor", "HighlightColor", + DEF_GRAPH_HIGHLIGHT_COLOR, Blt_Offset(Graph, highlightColor), 0}, + {BLT_CONFIG_PIXELS_NNEG, "-highlightthickness", "highlightThickness", + "HighlightThickness", DEF_GRAPH_HIGHLIGHT_WIDTH, + Blt_Offset(Graph, highlightWidth), BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_BITMASK, "-unmaphiddenelements", "unmapHiddenElements", + "UnmapHiddenElements", DEF_GRAPH_UNMAP_HIDDEN_ELEMENTS, + Blt_Offset(Graph, flags), ALL_GRAPHS | BLT_CONFIG_DONT_SET_DEFAULT, + (Blt_CustomOption *)UNMAP_HIDDEN}, + {BLT_CONFIG_BOOLEAN, "-invertxy", "invertXY", "InvertXY", + DEF_GRAPH_INVERT_XY, Blt_Offset(Graph, inverted), + BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_JUSTIFY, "-justify", "justify", "Justify", DEF_GRAPH_JUSTIFY, + Blt_Offset(Graph, titleTextStyle.justify), BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_PIXELS_NNEG, "-leftmargin", "leftMargin", "Margin", + DEF_GRAPH_MARGIN, Blt_Offset(Graph, leftMargin.reqSize), + BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_STRING, "-leftvariable", "leftVariable", "LeftVariable", + DEF_GRAPH_MARGIN_VAR, Blt_Offset(Graph, leftMargin.varName), + BLT_CONFIG_NULL_OK}, + {BLT_CONFIG_SYNONYM, "-lm", "leftMargin", (char *)NULL, (char *)NULL, 0, 0}, + {BLT_CONFIG_BACKGROUND, "-plotbackground", "plotBackground", "Background", + DEF_GRAPH_PLOT_BACKGROUND, Blt_Offset(Graph, plotBg), 0}, + {BLT_CONFIG_PIXELS_NNEG, "-plotborderwidth", "plotBorderWidth", + "PlotBorderWidth", DEF_GRAPH_PLOT_BORDERWIDTH, + Blt_Offset(Graph, plotBW), BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_PAD, "-plotpadx", "plotPadX", "PlotPad", DEF_GRAPH_PLOT_PADX, + Blt_Offset(Graph, xPad), BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_PAD, "-plotpady", "plotPadY", "PlotPad", DEF_GRAPH_PLOT_PADY, + Blt_Offset(Graph, yPad), BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_RELIEF, "-plotrelief", "plotRelief", "Relief", + DEF_GRAPH_PLOT_RELIEF, Blt_Offset(Graph, plotRelief), + BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_RELIEF, "-relief", "relief", "Relief", DEF_GRAPH_RELIEF, + Blt_Offset(Graph, relief), BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_PIXELS_NNEG, "-rightmargin", "rightMargin", "Margin", + DEF_GRAPH_MARGIN, Blt_Offset(Graph, rightMargin.reqSize), + BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_STRING, "-rightvariable", "rightVariable", "RightVariable", + DEF_GRAPH_MARGIN_VAR, Blt_Offset(Graph, rightMargin.varName), + BLT_CONFIG_NULL_OK}, + {BLT_CONFIG_SYNONYM, "-rm", "rightMargin", (char *)NULL, (char *)NULL, 0,0}, + {BLT_CONFIG_BOOLEAN, "-stackaxes", "stackAxes", "StackAxes", + DEF_GRAPH_STACK_AXES, Blt_Offset(Graph, stackAxes), + BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_STRING, "-takefocus", "takeFocus", "TakeFocus", + DEF_GRAPH_TAKE_FOCUS, Blt_Offset(Graph, takeFocus), BLT_CONFIG_NULL_OK}, + {BLT_CONFIG_STRING, "-title", "title", "Title", DEF_GRAPH_TITLE, + Blt_Offset(Graph, title), BLT_CONFIG_NULL_OK}, + {BLT_CONFIG_SYNONYM, "-tm", "topMargin", (char *)NULL, (char *)NULL, 0, 0}, + {BLT_CONFIG_PIXELS_NNEG, "-topmargin", "topMargin", "Margin", + DEF_GRAPH_MARGIN, Blt_Offset(Graph, topMargin.reqSize), + BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_STRING, "-topvariable", "topVariable", "TopVariable", + DEF_GRAPH_MARGIN_VAR, Blt_Offset(Graph, topMargin.varName), + BLT_CONFIG_NULL_OK}, + {BLT_CONFIG_PIXELS_NNEG, "-width", "width", "Width", DEF_GRAPH_WIDTH, + Blt_Offset(Graph, reqWidth), 0}, + {BLT_CONFIG_PIXELS_NNEG, "-plotwidth", "plotWidth", "PlotWidth", + (char *)NULL, Blt_Offset(Graph, reqPlotWidth), + BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_PIXELS_NNEG, "-plotheight", "plotHeight", "PlotHeight", + (char *)NULL, Blt_Offset(Graph, reqPlotHeight), + BLT_CONFIG_DONT_SET_DEFAULT}, + {BLT_CONFIG_END, NULL, NULL, NULL, NULL, 0, 0} +}; + +static Blt_SwitchParseProc ObjToFormat; +static Blt_SwitchCustom formatSwitch = +{ + ObjToFormat, NULL, (ClientData)0, +}; + +typedef struct { + const char *name; + int width, height; + int format; +} SnapSwitches; + +enum SnapFormats { FORMAT_PICTURE, FORMAT_PHOTO, FORMAT_EMF, FORMAT_WMF }; + +static Blt_SwitchSpec snapSwitches[] = +{ + {BLT_SWITCH_INT_POS, "-width", "width", + Blt_Offset(SnapSwitches, width), 0}, + {BLT_SWITCH_INT_POS, "-height", "height", + Blt_Offset(SnapSwitches, height), 0}, + {BLT_SWITCH_CUSTOM, "-format", "format", + Blt_Offset(SnapSwitches, format), 0, 0, &formatSwitch}, + {BLT_SWITCH_END} +}; + +static Tcl_IdleProc DisplayGraph; +static Tcl_FreeProc DestroyGraph; +static Tk_EventProc GraphEventProc; +Tcl_ObjCmdProc Blt_GraphInstCmdProc; + +static Blt_BindPickProc PickEntry; +static Tcl_ObjCmdProc StripchartCmd; +static Tcl_ObjCmdProc BarchartCmd; +static Tcl_ObjCmdProc GraphCmd; +static Tcl_CmdDeleteProc GraphInstCmdDeleteProc; + +/* + *--------------------------------------------------------------------------- + * + * Blt_UpdateGraph -- + * + * Tells the Tk dispatcher to call the graph display routine at the next + * idle point. This request is made only if the window is displayed and + * no other redraw request is pending. + * + * Results: None. + * + * Side effects: + * The window is eventually redisplayed. + * + *--------------------------------------------------------------------------- + */ +void +Blt_UpdateGraph(ClientData clientData) +{ + Graph *graphPtr = clientData; + + graphPtr->flags |= REDRAW_WORLD; + if ((graphPtr->tkwin != NULL) && !(graphPtr->flags & REDRAW_PENDING)) { + Tcl_DoWhenIdle(DisplayGraph, graphPtr); + graphPtr->flags |= REDRAW_PENDING; + } +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_EventuallyRedrawGraph -- + * + * Tells the Tk dispatcher to call the graph display routine at the next + * idle point. This request is made only if the window is displayed and + * no other redraw request is pending. + * + * Results: None. + * + * Side effects: + * The window is eventually redisplayed. + * + *--------------------------------------------------------------------------- + */ +void +Blt_EventuallyRedrawGraph(Graph *graphPtr) +{ + if ((graphPtr->tkwin != NULL) && !(graphPtr->flags & REDRAW_PENDING)) { + Tcl_DoWhenIdle(DisplayGraph, graphPtr); + graphPtr->flags |= REDRAW_PENDING; + } +} + +const char * +Blt_GraphClassName(ClassId classId) +{ + if ((classId >= CID_NONE) && (classId <= CID_MARKER_WINDOW)) { + return objectClassNames[classId]; + } + return NULL; +} + +void +Blt_GraphSetObjectClass(GraphObj *graphObjPtr, ClassId classId) +{ + graphObjPtr->classId = classId; + graphObjPtr->className = Blt_GraphClassName(classId); +} + +/* + *--------------------------------------------------------------------------- + * + * GraphEventProc -- + * + * This procedure is invoked by the Tk dispatcher for various events on + * graphs. + * + * Results: + * None. + * + * Side effects: + * When the window gets deleted, internal structures get cleaned up. + * When it gets exposed, the graph is eventually redisplayed. + * + *--------------------------------------------------------------------------- + */ +static void +GraphEventProc(ClientData clientData, XEvent *eventPtr) +{ + Graph *graphPtr = clientData; + + if (eventPtr->type == Expose) { + if (eventPtr->xexpose.count == 0) { + graphPtr->flags |= REDRAW_WORLD; + Blt_EventuallyRedrawGraph(graphPtr); + } + } else if ((eventPtr->type == FocusIn) || (eventPtr->type == FocusOut)) { + if (eventPtr->xfocus.detail != NotifyInferior) { + if (eventPtr->type == FocusIn) { + graphPtr->flags |= FOCUS; + } else { + graphPtr->flags &= ~FOCUS; + } + graphPtr->flags |= REDRAW_WORLD; + Blt_EventuallyRedrawGraph(graphPtr); + } + } else if (eventPtr->type == DestroyNotify) { + if (graphPtr->tkwin != NULL) { + Blt_DeleteWindowInstanceData(graphPtr->tkwin); + graphPtr->tkwin = NULL; + Tcl_DeleteCommandFromToken(graphPtr->interp, graphPtr->cmdToken); + } + if (graphPtr->flags & REDRAW_PENDING) { + Tcl_CancelIdleCall(DisplayGraph, graphPtr); + } + Tcl_EventuallyFree(graphPtr, DestroyGraph); + } else if (eventPtr->type == ConfigureNotify) { + graphPtr->flags |= (MAP_WORLD | REDRAW_WORLD); + Blt_EventuallyRedrawGraph(graphPtr); + } +} + +/* + *--------------------------------------------------------------------------- + * + * GraphInstCmdDeleteProc -- + * + * This procedure is invoked when a widget command is deleted. If the + * widget isn't already in the process of being destroyed, this command + * destroys it. + * + * Results: + * None. + * + * Side effects: + * The widget is destroyed. + * + *--------------------------------------------------------------------------- + */ +static void +GraphInstCmdDeleteProc(ClientData clientData) /* Pointer to widget record. */ +{ + Graph *graphPtr = clientData; + + if (graphPtr->tkwin != NULL) { /* NULL indicates window has already + * been destroyed. */ + Tk_Window tkwin; + + tkwin = graphPtr->tkwin; + graphPtr->tkwin = NULL; + Blt_DeleteWindowInstanceData(tkwin); + Tk_DestroyWindow(tkwin); + } +} + +/* + *--------------------------------------------------------------------------- + * + * AdjustAxisPointers -- + * + * Sets the axis pointers according to whether the axis is inverted on + * not. The axis sites are also reset. + * + * Results: + * None. + * + *--------------------------------------------------------------------------- + */ +static void +AdjustAxisPointers(Graph *graphPtr) +{ + if (graphPtr->inverted) { + graphPtr->leftMargin.axes = graphPtr->axisChain[0]; + graphPtr->bottomMargin.axes = graphPtr->axisChain[1]; + graphPtr->rightMargin.axes = graphPtr->axisChain[2]; + graphPtr->topMargin.axes = graphPtr->axisChain[3]; + } else { + graphPtr->leftMargin.axes = graphPtr->axisChain[1]; + graphPtr->bottomMargin.axes = graphPtr->axisChain[0]; + graphPtr->rightMargin.axes = graphPtr->axisChain[3]; + graphPtr->topMargin.axes = graphPtr->axisChain[2]; + } +} + +static int +InitPens(Graph *graphPtr) +{ + Blt_InitHashTable(&graphPtr->penTable, BLT_STRING_KEYS); + if (Blt_CreatePen(graphPtr, "activeLine", CID_ELEM_LINE, 0, NULL) == NULL) { + return TCL_ERROR; + } + if (Blt_CreatePen(graphPtr, "activeBar", CID_ELEM_BAR, 0, NULL) == NULL) { + return TCL_ERROR; + } + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_GraphTags -- + * + * Sets the binding tags for a graph obj. This routine is called by Tk + * when an event occurs in the graph. It fills an array of pointers with + * bind tag addresses. + * + * The object addresses are strings hashed in one of two tag tables: one + * for elements and the another for markers. Note that there's only one + * binding table for elements and markers. [We don't want to trigger + * both a marker and element bind command for the same event.] But we + * don't want a marker and element with the same tag name to activate the + * others bindings. A tag "all" for markers should mean all markers, not + * all markers and elements. As a result, element and marker tags are + * stored in separate hash tables, which means we can't generate the same + * tag address for both an elements and marker, even if they have the + * same name. + * + * Results: + * None. + * + * Side effects: + * This information will be used by the binding code in bltUtil.c to + * determine what graph objects match the current event. The tags are + * placed in tagArr and *nTagsPtr is set with the number of tags found. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +void +Blt_GraphTags( + Blt_BindTable table, + ClientData object, + ClientData context, /* Not used. */ + Blt_List list) +{ + GraphObj *graphObjPtr; + MakeTagProc *tagProc; + Graph *graphPtr; + + graphPtr = (Graph *)Blt_GetBindingData(table); + + /* + * All graph objects (markers, elements, axes, etc) have the same starting + * fields in their structures, such as "classId", "name", "className", and + * "tags". + */ + graphObjPtr = (GraphObj *)object; + + switch (graphObjPtr->classId) { + case CID_ELEM_BAR: + case CID_ELEM_CONTOUR: + case CID_ELEM_LINE: + case CID_ELEM_STRIP: + tagProc = Blt_MakeElementTag; + break; + case CID_AXIS_X: + case CID_AXIS_Y: + tagProc = Blt_MakeAxisTag; + break; + case CID_MARKER_BITMAP: + case CID_MARKER_IMAGE: + case CID_MARKER_LINE: + case CID_MARKER_POLYGON: + case CID_MARKER_TEXT: + case CID_MARKER_WINDOW: + tagProc = Blt_MakeMarkerTag; + break; + case CID_NONE: + panic("unknown object type"); + tagProc = NULL; + break; + default: + panic("bogus object type"); + tagProc = NULL; + break; + } + assert(graphObjPtr->name != NULL); + + /* Always add the name of the object to the tag array. */ + Blt_List_Append(list, (*tagProc)(graphPtr, graphObjPtr->name), 0); + Blt_List_Append(list, (*tagProc)(graphPtr, graphObjPtr->className), 0); + if (graphObjPtr->tags != NULL) { + const char **p; + + for (p = graphObjPtr->tags; *p != NULL; p++) { + Blt_List_Append(list, (*tagProc) (graphPtr, *p), 0); + } + } +} + +/* + * Find the closest point from the set of displayed elements, searching + * the display list from back to front. That way, if the points from + * two different elements overlay each other exactly, the one that's on + * top (visible) is picked. + */ +/*ARGSUSED*/ +static ClientData +PickEntry(ClientData clientData, int x, int y, ClientData *contextPtr) +{ + Graph *graphPtr = clientData; + Blt_ChainLink link; + Element *elemPtr; + Marker *markerPtr; + Region2d exts; + + if (graphPtr->flags & MAP_ALL) { + return NULL; /* Don't pick anything until the next + * redraw occurs. */ + } + Blt_GraphExtents(graphPtr, &exts); + + if ((x >= exts.right) || (x < exts.left) || + (y >= exts.bottom) || (y < exts.top)) { + /* + * Sample coordinate is in one of the graph margins. Can only pick an + * axis. + */ + return Blt_NearestAxis(graphPtr, x, y); + } + /* + * From top-to-bottom check: + * 1. markers drawn on top (-under false). + * 2. elements using its display list back to front. + * 3. markers drawn under element (-under true). + */ + markerPtr = Blt_NearestMarker(graphPtr, x, y, FALSE); + if (markerPtr != NULL) { + return markerPtr; /* Found a marker (-under false). */ + } + { + ClosestSearch search; + + search.along = SEARCH_BOTH; + search.halo = graphPtr->halo; + search.index = -1; + search.x = x; + search.y = y; + search.dist = (double)(search.halo + 1); + search.mode = SEARCH_AUTO; + + for (link = Blt_Chain_LastLink(graphPtr->elements.displayList); + link != NULL; link = Blt_Chain_PrevLink(link)) { + elemPtr = Blt_Chain_GetValue(link); + if (elemPtr->flags & (HIDE|MAP_ITEM)) { + continue; + } + if (elemPtr->state == STATE_NORMAL) { + (*elemPtr->procsPtr->closestProc) (graphPtr, elemPtr, &search); + } + } + if (search.dist <= (double)search.halo) { + return search.elemPtr; /* Found an element within the minimum + * halo distance. */ + } + } + markerPtr = Blt_NearestMarker(graphPtr, x, y, TRUE); + if (markerPtr != NULL) { + return markerPtr; /* Found a marker (-under true) */ + } + return NULL; /* Nothing found. */ +} + +/* + *--------------------------------------------------------------------------- + * + * ConfigureGraph -- + * + * Allocates resources for the graph. + * + * Results: + * None. + * + * Side effects: + * Configuration information, such as text string, colors, font, etc. get + * set for graphPtr; old resources get freed, if there were any. The + * graph is redisplayed. + * + *--------------------------------------------------------------------------- + */ +static void +ConfigureGraph(Graph *graphPtr) +{ + XColor *colorPtr; + GC newGC; + XGCValues gcValues; + unsigned long gcMask; + + /* Don't allow negative bar widths. Reset to an arbitrary value (0.1) */ + if (graphPtr->barWidth <= 0.0f) { + graphPtr->barWidth = 0.8f; + } + graphPtr->inset = graphPtr->borderWidth + graphPtr->highlightWidth; + if ((graphPtr->reqHeight != Tk_ReqHeight(graphPtr->tkwin)) || + (graphPtr->reqWidth != Tk_ReqWidth(graphPtr->tkwin))) { + Tk_GeometryRequest(graphPtr->tkwin, graphPtr->reqWidth, + graphPtr->reqHeight); + } + Tk_SetInternalBorder(graphPtr->tkwin, graphPtr->borderWidth); + colorPtr = Blt_BackgroundBorderColor(graphPtr->normalBg); + + graphPtr->titleWidth = graphPtr->titleHeight = 0; + if (graphPtr->title != NULL) { + unsigned int w, h; + + Blt_Ts_GetExtents(&graphPtr->titleTextStyle, graphPtr->title, &w, &h); + graphPtr->titleHeight = h; + } + + /* + * Create GCs for interior and exterior regions, and a background GC for + * clearing the margins with XFillRectangle + */ + + /* Margin GC */ + + gcValues.foreground = + Blt_Ts_GetForeground(graphPtr->titleTextStyle)->pixel; + gcValues.background = colorPtr->pixel; + gcMask = (GCForeground | GCBackground); + newGC = Tk_GetGC(graphPtr->tkwin, gcMask, &gcValues); + if (graphPtr->drawGC != NULL) { + Tk_FreeGC(graphPtr->display, graphPtr->drawGC); + } + graphPtr->drawGC = newGC; + + if (graphPtr->plotBg != NULL) { + Blt_SetBackgroundChangedProc(graphPtr->plotBg, Blt_UpdateGraph, + graphPtr); + } + if (graphPtr->normalBg != NULL) { + Blt_SetBackgroundChangedProc(graphPtr->normalBg, Blt_UpdateGraph, + graphPtr); + } + if (Blt_ConfigModified(configSpecs, "-invertxy", (char *)NULL)) { + + /* + * If the -inverted option changed, we need to readjust the pointers + * to the axes and recompute the their scales. + */ + + AdjustAxisPointers(graphPtr); + graphPtr->flags |= RESET_AXES; + } + if ((!graphPtr->backingStore) && (graphPtr->cache != None)) { + /* + * Free the pixmap if we're not buffering the display of elements + * anymore. + */ + Tk_FreePixmap(graphPtr->display, graphPtr->cache); + graphPtr->cache = None; + } + /* + * Reconfigure the crosshairs, just in case the background color of the + * plotarea has been changed. + */ + Blt_ConfigureCrosshairs(graphPtr); + + /* + * Update the layout of the graph (and redraw the elements) if any of the + * following graph options which affect the size of * the plotting area + * has changed. + * + * -aspect + * -borderwidth, -plotborderwidth + * -font, -title + * -width, -height + * -invertxy + * -bottommargin, -leftmargin, -rightmargin, -topmargin, + * -barmode, -barwidth + */ + if (Blt_ConfigModified(configSpecs, "-invertxy", "-title", "-font", + "-*margin", "-*width", "-height", "-barmode", "-*pad*", + "-aspect", "-*borderwidth", "-plot*", "-*width", "-*height", + "-unmaphiddenelements", (char *)NULL)) { + graphPtr->flags |= RESET_WORLD | CACHE_DIRTY; + } + if (Blt_ConfigModified(configSpecs, "-plot*", "-*background", + (char *)NULL)) { + graphPtr->flags |= CACHE_DIRTY; + } + graphPtr->flags |= REDRAW_WORLD; +} + +/* + *--------------------------------------------------------------------------- + * + * DestroyGraph -- + * + * This procedure is invoked by Tcl_EventuallyFree or Tcl_Release to + * clean up the internal structure of a graph at a safe time (when no-one + * is using it anymore). + * + * Results: + * None. + * + * Side effects: + * Everything associated with the widget is freed up. + * + *--------------------------------------------------------------------------- + */ +static void +DestroyGraph(DestroyData dataPtr) +{ + Graph *graphPtr = (Graph *)dataPtr; + + Blt_FreeOptions(configSpecs, (char *)graphPtr, graphPtr->display, 0); + /* + * Destroy the individual components of the graph: elements, markers, + * axes, legend, display lists etc. Be careful to remove them in + * order. For example, axes are used by elements and markers, so they have + * to be removed after the markers and elements. Same it true with the + * legend and pens (they use elements), so can't be removed until the + * elements are destroyed. + */ + Blt_DestroyMarkers(graphPtr); + Blt_DestroyElements(graphPtr); + Blt_DestroyLegend(graphPtr); + Blt_DestroyAxes(graphPtr); + Blt_DestroyPens(graphPtr); + Blt_DestroyCrosshairs(graphPtr); + Blt_DestroyPageSetup(graphPtr); + Blt_DestroyBarSets(graphPtr); + /* Destroy table clients after elements are destroyed. */ + Blt_DestroyTableClients(graphPtr); + if (graphPtr->bindTable != NULL) { + Blt_DestroyBindingTable(graphPtr->bindTable); + } + + /* Release allocated X resources and memory. */ + if (graphPtr->drawGC != NULL) { + Tk_FreeGC(graphPtr->display, graphPtr->drawGC); + } + Blt_Ts_FreeStyle(graphPtr->display, &graphPtr->titleTextStyle); + if (graphPtr->cache != None) { + Tk_FreePixmap(graphPtr->display, graphPtr->cache); + } + Blt_Free(graphPtr); +} + +/* + *--------------------------------------------------------------------------- + * + * CreateGraph -- + * + * This procedure creates and initializes a new widget. + * + * Results: + * The return value is a pointer to a structure describing the new + * widget. If an error occurred, then the return value is NULL and an + * error message is left in interp->result. + * + * Side effects: + * Memory is allocated, a Tk_Window is created, etc. + * + *--------------------------------------------------------------------------- + */ + +static Graph * +CreateGraph(Tcl_Interp *interp, int objc, Tcl_Obj *const *objv, ClassId classId) +{ + Graph *graphPtr; + Tk_Window tkwin; + + tkwin = Tk_CreateWindowFromPath(interp, Tk_MainWindow(interp), + Tcl_GetString(objv[1]), (char *)NULL); + if (tkwin == NULL) { + return NULL; + } + graphPtr = Blt_AssertCalloc(1, sizeof(Graph)); + + /* Initialize the graph data structure. */ + + graphPtr->tkwin = tkwin; + graphPtr->display = Tk_Display(tkwin); + graphPtr->interp = interp; + graphPtr->classId = classId; + graphPtr->backingStore = TRUE; + graphPtr->doubleBuffer = TRUE; + graphPtr->borderWidth = 2; + graphPtr->plotBW = 1; + graphPtr->highlightWidth = 2; + graphPtr->plotRelief = TK_RELIEF_SOLID; + graphPtr->relief = TK_RELIEF_FLAT; + graphPtr->flags = RESET_WORLD; + graphPtr->nextMarkerId = 1; + graphPtr->padLeft = graphPtr->padRight = 0; + graphPtr->padTop = graphPtr->padBottom = 0; + graphPtr->bottomMargin.site = MARGIN_BOTTOM; + graphPtr->leftMargin.site = MARGIN_LEFT; + graphPtr->topMargin.site = MARGIN_TOP; + graphPtr->rightMargin.site = MARGIN_RIGHT; + Blt_Ts_InitStyle(graphPtr->titleTextStyle); + Blt_Ts_SetAnchor(graphPtr->titleTextStyle, TK_ANCHOR_N); + + Blt_InitHashTable(&graphPtr->axes.table, BLT_STRING_KEYS); + Blt_InitHashTable(&graphPtr->axes.tagTable, BLT_STRING_KEYS); + Blt_InitHashTable(&graphPtr->elements.table, BLT_STRING_KEYS); + Blt_InitHashTable(&graphPtr->elements.tagTable, BLT_STRING_KEYS); + Blt_InitHashTable(&graphPtr->markers.table, BLT_STRING_KEYS); + Blt_InitHashTable(&graphPtr->markers.tagTable, BLT_STRING_KEYS); + Blt_InitHashTable(&graphPtr->dataTables, BLT_STRING_KEYS); + graphPtr->elements.displayList = Blt_Chain_Create(); + graphPtr->markers.displayList = Blt_Chain_Create(); + graphPtr->axes.displayList = Blt_Chain_Create(); + + switch (classId) { + case CID_ELEM_LINE: + Tk_SetClass(tkwin, "Graph"); + break; + case CID_ELEM_BAR: + Tk_SetClass(tkwin, "Barchart"); + break; + case CID_ELEM_STRIP: + Tk_SetClass(tkwin, "Stripchart"); + default: + Tk_SetClass(tkwin, "???"); + break; + } + Blt_SetWindowInstanceData(tkwin, graphPtr); + + if (InitPens(graphPtr) != TCL_OK) { + goto error; + } + if (Blt_ConfigureWidgetFromObj(interp, tkwin, configSpecs, objc - 2, + objv + 2, (char *)graphPtr, 0) != TCL_OK) { + goto error; + } + if (Blt_DefaultAxes(graphPtr) != TCL_OK) { + goto error; + } + AdjustAxisPointers(graphPtr); + + if (Blt_CreatePageSetup(graphPtr) != TCL_OK) { + goto error; + } + if (Blt_CreateCrosshairs(graphPtr) != TCL_OK) { + goto error; + } + if (Blt_CreateLegend(graphPtr) != TCL_OK) { + goto error; + } + Tk_CreateEventHandler(graphPtr->tkwin, + ExposureMask | StructureNotifyMask | FocusChangeMask, GraphEventProc, + graphPtr); + + graphPtr->cmdToken = Tcl_CreateObjCommand(interp, Tcl_GetString(objv[1]), + Blt_GraphInstCmdProc, graphPtr, GraphInstCmdDeleteProc); + ConfigureGraph(graphPtr); + graphPtr->bindTable = Blt_CreateBindingTable(interp, tkwin, graphPtr, + PickEntry, Blt_GraphTags); + + Tcl_SetObjResult(interp, objv[1]); + return graphPtr; + + error: + DestroyGraph((DestroyData)graphPtr); + return NULL; +} + +/* Widget sub-commands */ + +/*ARGSUSED*/ +static int +XAxisOp(Graph *graphPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) +{ + int margin; + + margin = (graphPtr->inverted) ? MARGIN_LEFT : MARGIN_BOTTOM; + return Blt_AxisOp(interp, graphPtr, margin, objc, objv); +} + +static int +X2AxisOp(Graph *graphPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) +{ + int margin; + + margin = (graphPtr->inverted) ? MARGIN_RIGHT : MARGIN_TOP; + return Blt_AxisOp(interp, graphPtr, margin, objc, objv); +} + +static int +YAxisOp(Graph *graphPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) +{ + int margin; + + margin = (graphPtr->inverted) ? MARGIN_BOTTOM : MARGIN_LEFT; + return Blt_AxisOp(interp, graphPtr, margin, objc, objv); +} + +static int +Y2AxisOp(Graph *graphPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) +{ + int margin; + + margin = (graphPtr->inverted) ? MARGIN_TOP : MARGIN_RIGHT; + return Blt_AxisOp(interp, graphPtr, margin, objc, objv); +} + +static int +BarOp(Graph *graphPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) +{ + return Blt_ElementOp(graphPtr, interp, objc, objv, CID_ELEM_BAR); +} + +/*ARGSUSED*/ +static int +LineOp(Graph *graphPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) +{ + return Blt_ElementOp(graphPtr, interp, objc, objv, CID_ELEM_LINE); +} + +static int +ElementOp(Graph *graphPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) +{ + return Blt_ElementOp(graphPtr, interp, objc, objv, graphPtr->classId); +} + +static int +ConfigureOp(Graph *graphPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) +{ + int flags; + + flags = BLT_CONFIG_OBJV_ONLY; + if (objc == 2) { + return Blt_ConfigureInfoFromObj(interp, graphPtr->tkwin, configSpecs, + (char *)graphPtr, (Tcl_Obj *)NULL, flags); + } else if (objc == 3) { + return Blt_ConfigureInfoFromObj(interp, graphPtr->tkwin, configSpecs, + (char *)graphPtr, objv[2], flags); + } else { + if (Blt_ConfigureWidgetFromObj(interp, graphPtr->tkwin, configSpecs, + objc - 2, objv + 2, (char *)graphPtr, flags) != TCL_OK) { + return TCL_ERROR; + } + ConfigureGraph(graphPtr); + Blt_EventuallyRedrawGraph(graphPtr); + return TCL_OK; + } +} + +/* ARGSUSED*/ +static int +CgetOp(Graph *graphPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) +{ + return Blt_ConfigureValueFromObj(interp, graphPtr->tkwin, configSpecs, + (char *)graphPtr, objv[2], 0); +} + +/* + *--------------------------------------------------------------------------- + * + * ExtentsOp -- + * + * Reports the size of one of several items within the graph. The + * following are valid items: + * + * "bottommargin" Height of the bottom margin + * "leftmargin" Width of the left margin + * "legend" x y w h of the legend + * "plotarea" x y w h of the plotarea + * "plotheight" Height of the plot area + * "rightmargin" Width of the right margin + * "topmargin" Height of the top margin + * "plotwidth" Width of the plot area + * + * Results: + * Always returns TCL_OK. + * + *--------------------------------------------------------------------------- + */ +/* ARGSUSED*/ +static int +ExtentsOp(Graph *graphPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) +{ + const char *string; + char c; + int length; + + string = Tcl_GetStringFromObj(objv[2], &length); + c = string[0]; + if ((c == 'p') && (length > 4) && + (strncmp("plotheight", string, length) == 0)) { + int height; + + height = graphPtr->bottom - graphPtr->top + 1; + Tcl_SetIntObj(Tcl_GetObjResult(interp), height); + } else if ((c == 'p') && (length > 4) && + (strncmp("plotwidth", string, length) == 0)) { + int width; + + width = graphPtr->right - graphPtr->left + 1; + Tcl_SetIntObj(Tcl_GetObjResult(interp), width); + } else if ((c == 'p') && (length > 4) && + (strncmp("plotarea", string, length) == 0)) { + Tcl_Obj *listObjPtr; + + listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL); + Tcl_ListObjAppendElement(interp, listObjPtr, + Tcl_NewIntObj(graphPtr->left)); + Tcl_ListObjAppendElement(interp, listObjPtr, + Tcl_NewIntObj(graphPtr->top)); + Tcl_ListObjAppendElement(interp, listObjPtr, + Tcl_NewIntObj(graphPtr->right - graphPtr->left + 1)); + Tcl_ListObjAppendElement(interp, listObjPtr, + Tcl_NewIntObj(graphPtr->bottom - graphPtr->top + 1)); + Tcl_SetObjResult(interp, listObjPtr); + } else if ((c == 'l') && (length > 2) && + (strncmp("legend", string, length) == 0)) { + Tcl_Obj *listObjPtr; + + listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL); + Tcl_ListObjAppendElement(interp, listObjPtr, + Tcl_NewIntObj(Blt_Legend_X(graphPtr))); + Tcl_ListObjAppendElement(interp, listObjPtr, + Tcl_NewIntObj(Blt_Legend_Y(graphPtr))); + Tcl_ListObjAppendElement(interp, listObjPtr, + Tcl_NewIntObj(Blt_Legend_Width(graphPtr))); + Tcl_ListObjAppendElement(interp, listObjPtr, + Tcl_NewIntObj(Blt_Legend_Height(graphPtr))); + Tcl_SetObjResult(interp, listObjPtr); + } else if ((c == 'l') && (length > 2) && + (strncmp("leftmargin", string, length) == 0)) { + Tcl_SetIntObj(Tcl_GetObjResult(interp), graphPtr->leftMargin.width); + } else if ((c == 'r') && (length > 1) && + (strncmp("rightmargin", string, length) == 0)) { + Tcl_SetIntObj(Tcl_GetObjResult(interp), graphPtr->rightMargin.width); + } else if ((c == 't') && (length > 1) && + (strncmp("topmargin", string, length) == 0)) { + Tcl_SetIntObj(Tcl_GetObjResult(interp), graphPtr->topMargin.height); + } else if ((c == 'b') && (length > 1) && + (strncmp("bottommargin", string, length) == 0)) { + Tcl_SetIntObj(Tcl_GetObjResult(interp), graphPtr->bottomMargin.height); + } else { + Tcl_AppendResult(interp, "bad extent item \"", objv[2], + "\": should be plotheight, plotwidth, leftmargin, rightmargin, \ +topmargin, bottommargin, plotarea, or legend", (char *)NULL); + return TCL_ERROR; + } + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * InsideOp -- + * + * Returns true of false whether the given point is inside the plotting + * area (defined by left,bottom right, top). + * + * Results: + * Always returns TCL_OK. interp->result will contain the boolean string + * representation. + * + *--------------------------------------------------------------------------- + */ +/* ARGSUSED*/ +static int +InsideOp(Graph *graphPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) +{ + int x, y; + Region2d exts; + int result; + + if (Tcl_GetIntFromObj(interp, objv[2], &x) != TCL_OK) { + return TCL_ERROR; + } + if (Tcl_GetIntFromObj(interp, objv[3], &y) != TCL_OK) { + return TCL_ERROR; + } + Blt_GraphExtents(graphPtr, &exts); + result = PointInRegion(&exts, x, y); + Tcl_SetBooleanObj(Tcl_GetObjResult(interp), result); + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * InvtransformOp -- + * + * This procedure returns a list of the graph coordinate values + * corresponding with the given window X and Y coordinate positions. + * + * Results: + * Returns a standard TCL result. If an error occurred while parsing the + * window positions, TCL_ERROR is returned, and interp->result will + * contain the error message. Otherwise interp->result will contain a + * Tcl list of the x and y coordinates. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +InvtransformOp(Graph *graphPtr, Tcl_Interp *interp, int objc, + Tcl_Obj *const *objv) +{ + double x, y; + Point2d point; + Axis2d axes; + Tcl_Obj *listObjPtr; + + if ((Blt_ExprDoubleFromObj(interp, objv[2], &x) != TCL_OK) || + (Blt_ExprDoubleFromObj(interp, objv[3], &y) != TCL_OK)) { + return TCL_ERROR; + } + if (graphPtr->flags & RESET_AXES) { + Blt_ResetAxes(graphPtr); + } + /* Perform the reverse transformation, converting from window coordinates + * to graph data coordinates. Note that the point is always mapped to the + * bottom and left axes (which may not be what the user wants). */ + + /* Pick the first pair of axes */ + axes.x = Blt_GetFirstAxis(graphPtr->axisChain[0]); + axes.y = Blt_GetFirstAxis(graphPtr->axisChain[1]); + point = Blt_InvMap2D(graphPtr, x, y, &axes); + + listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL); + Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewDoubleObj(point.x)); + Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewDoubleObj(point.y)); + Tcl_SetObjResult(interp, listObjPtr); + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * TransformOp -- + * + * This procedure returns a list of the window coordinates corresponding + * with the given graph x and y coordinates. + * + * Results: + * Returns a standard TCL result. interp->result contains the list of + * the graph coordinates. If an error occurred while parsing the window + * positions, TCL_ERROR is returned, then interp->result will contain an + * error message. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +TransformOp(Graph *graphPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) +{ + double x, y; + Point2d point; + Axis2d axes; + Tcl_Obj *listObjPtr; + + if ((Blt_ExprDoubleFromObj(interp, objv[2], &x) != TCL_OK) || + (Blt_ExprDoubleFromObj(interp, objv[3], &y) != TCL_OK)) { + return TCL_ERROR; + } + if (graphPtr->flags & RESET_AXES) { + Blt_ResetAxes(graphPtr); + } + /* + * Perform the transformation from window to graph coordinates. Note that + * the points are always mapped onto the bottom and left axes (which may + * not be the what the user wants). + */ + axes.x = Blt_GetFirstAxis(graphPtr->axisChain[0]); + axes.y = Blt_GetFirstAxis(graphPtr->axisChain[1]); + + point = Blt_Map2D(graphPtr, x, y, &axes); + + listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL); + Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewIntObj(ROUND(point.x))); + Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewIntObj(ROUND(point.y))); + Tcl_SetObjResult(interp, listObjPtr); + return TCL_OK; +} + +#ifndef NO_PRINTER + +/* + *--------------------------------------------------------------------------- + * + * Print1Op -- + * + * Prints the equivalent of a screen snapshot of the graph to the + * designated printer. + * + * Results: + * Returns a standard TCL result. If an error occurred TCL_ERROR is + * returned and interp->result will contain an error message. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +Print1Op(Graph *graphPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) +{ + BITMAPINFO info; + void *data; + TkWinDCState state; + TkWinBitmap bd; + DIBSECTION ds; + Drawable drawable; + HBITMAP hBitmap; + HDC hDC; + DOCINFO di; + double pageWidth, pageHeight; + int result; + double scale, sx, sy; + int jobId; + + graphPtr->width = Tk_Width(graphPtr->tkwin); + graphPtr->height = Tk_Height(graphPtr->tkwin); + if ((graphPtr->width < 2) && (graphPtr->reqWidth > 0)) { + graphPtr->width = graphPtr->reqWidth; + } + if ((graphPtr->height < 2) && (graphPtr->reqHeight > 0)) { + graphPtr->height = graphPtr->reqHeight; + } + if (objc == 2) { + result = Blt_PrintDialog(interp, &drawable); + if (result == TCL_ERROR) { + return TCL_ERROR; + } + if (result == TCL_RETURN) { + return TCL_OK; + } + } else { + if (Blt_GetOpenPrinter(interp, Tcl_GetString(objv[2]), &drawable) + != TCL_OK) { + return TCL_ERROR; + } + } + /* + * This is a taken from Blt_SnapPhoto. The difference is that here we're + * using the DIBSection directly, without converting the section into a + * Picture. + */ + ZeroMemory(&info, sizeof(info)); + info.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + info.bmiHeader.biWidth = graphPtr->width; + info.bmiHeader.biHeight = graphPtr->height; + info.bmiHeader.biPlanes = 1; + info.bmiHeader.biBitCount = 32; + info.bmiHeader.biCompression = BI_RGB; + hDC = TkWinGetDrawableDC(graphPtr->display, Tk_WindowId(graphPtr->tkwin), + &state); + hBitmap = CreateDIBSection(hDC, &info, DIB_RGB_COLORS, &data, NULL, 0); + TkWinReleaseDrawableDC(Tk_WindowId(graphPtr->tkwin), hDC, &state); + + /* + * Create our own drawable by hand using the DIB we just created. We'll + * then draw into it using the standard drawing functions. + */ + bd.type = TWD_BITMAP; + bd.handle = hBitmap; + bd.colormap = DefaultColormap(graphPtr->display, + DefaultScreen(graphPtr->display)); + bd.depth = Tk_Depth(graphPtr->tkwin); + + graphPtr->flags |= RESET_WORLD; + Blt_DrawGraph(graphPtr, (Drawable)&bd); + + /* + * Now that the DIB contains the image of the graph, get the the data bits + * and write them to the printer device, stretching the image to the fit + * the printer's resolution. + */ + result = TCL_ERROR; + if (GetObject(hBitmap, sizeof(DIBSECTION), &ds) == 0) { + Tcl_AppendResult(interp, "can't get object: ", Blt_LastError(), + (char *)NULL); + goto done; + } + hDC = ((TkWinDC *) drawable)->hdc; + /* Get the resolution of the printer device. */ + sx = (double)GetDeviceCaps(hDC, HORZRES) / (double)graphPtr->width; + sy = (double)GetDeviceCaps(hDC, VERTRES) / (double)graphPtr->height; + scale = MIN(sx, sy); + pageWidth = scale * graphPtr->width; + pageHeight = scale * graphPtr->height; + + ZeroMemory(&di, sizeof(di)); + di.cbSize = sizeof(di); + di.lpszDocName = "Graph Contents"; + jobId = StartDoc(hDC, &di); + if (jobId <= 0) { + Tcl_AppendResult(interp, "can't start document: ", Blt_LastError(), + (char *)NULL); + goto done; + } + if (StartPage(hDC) <= 0) { + Tcl_AppendResult(interp, "error starting page: ", Blt_LastError(), + (char *)NULL); + goto done; + } + StretchDIBits(hDC, 0, 0, ROUND(pageWidth), ROUND(pageHeight), 0, 0, + graphPtr->width, graphPtr->height, ds.dsBm.bmBits, + (LPBITMAPINFO)&ds.dsBmih, DIB_RGB_COLORS, SRCCOPY); + EndPage(hDC); + EndDoc(hDC); + result = TCL_OK; + done: + DeleteBitmap(hBitmap); + return result; +} + +/* + *--------------------------------------------------------------------------- + * + * Print2Op -- + * + * Prints directly to the designated printer device. + * + * Results: + * Returns a standard TCL result. If an error occurred, TCL_ERROR is + * returned and interp->result will contain an error message. + * + *--------------------------------------------------------------------------- + */ +static int +Print2Op(Graph *graphPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) +{ + Drawable drawable; + int result; + + graphPtr->width = Tk_Width(graphPtr->tkwin); + graphPtr->height = Tk_Height(graphPtr->tkwin); + if ((graphPtr->width < 2) && (graphPtr->reqWidth > 0)) { + graphPtr->width = graphPtr->reqWidth; + } + if ((graphPtr->height < 2) && (graphPtr->reqHeight > 0)) { + graphPtr->height = graphPtr->reqHeight; + } + if (objc == 2) { + result = Blt_PrintDialog(interp, &drawable); + if (result == TCL_ERROR) { + return TCL_ERROR; + } + if (result == TCL_RETURN) { + return TCL_OK; + } + } else { + result = Blt_GetOpenPrinter(interp, Tcl_GetString(objv[2]), &drawable); + } + if (result == TCL_OK) { + int oldMode; + HDC hDC; + double xRatio, yRatio; + TkWinDC *drawPtr; + int w, h; + + drawPtr = (TkWinDC *) drawable; + hDC = drawPtr->hdc; + Blt_GetPrinterScale(hDC, &xRatio, &yRatio); + oldMode = SetMapMode(hDC, MM_ISOTROPIC); + if (oldMode == 0) { + Tcl_AppendResult(interp, "can't set mode for printer DC: ", + Blt_LastError(), (char *)NULL); + return TCL_ERROR; + } + w = (int)round(graphPtr->width * xRatio); + h = (int)round(graphPtr->height * yRatio); + SetViewportExtEx(hDC, w, h, NULL); + SetWindowExtEx(hDC, graphPtr->width, graphPtr->height, NULL); + + Blt_StartPrintJob(interp, drawable); + graphPtr->flags |= RESET_WORLD; + Blt_DrawGraph(graphPtr, drawable); + Blt_EndPrintJob(interp, drawable); + } + return result; +} + +#endif /* NO_PRINTER */ + +/* + *--------------------------------------------------------------------------- + * + * ObjToFormat -- + * + * Convert a string represent a node number into its integer value. + * + * Results: + * The return value is a standard TCL result. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +ObjToFormat( + ClientData clientData, /* Not used.*/ + Tcl_Interp *interp, /* Interpreter to send results back to */ + const char *switchName, /* Not used. */ + Tcl_Obj *objPtr, /* String representation */ + char *record, /* Structure record */ + int offset, /* Offset to field in structure */ + int flags) /* Not used. */ +{ + int *formatPtr = (int *)(record + offset); + char c; + const char *string; + + string = Tcl_GetString(objPtr); + c = string[0]; + if ((c == 'p') && (strcmp(string, "picture") == 0)) { + *formatPtr = FORMAT_PHOTO; + } else if ((c == 'p') && (strcmp(string, "photo") == 0)) { + *formatPtr = FORMAT_PHOTO; +#ifdef WIN32 + } else if ((c == 'e') && (strcmp(string, "emf") == 0)) { + *formatPtr = FORMAT_EMF; + } else if ((c == 'w') && (strcmp(string, "wmf") == 0)) { + *formatPtr = FORMAT_WMF; +#endif /* WIN32 */ + } else { +#ifdef WIN32 + Tcl_AppendResult(interp, "bad format \"", string, + "\": should be picture, photo, emf, or wmf.", (char *)NULL); +#else + Tcl_AppendResult(interp, "bad format \"", string, + "\": should be picture or photo.", (char *)NULL); +#endif /* WIN32 */ + return TCL_ERROR; + } + return TCL_OK; +} + +#ifdef WIN32 +static int InitMetaFileHeader( + Tk_Window tkwin, + int width, int height, + APMHEADER *mfhPtr) +{ + unsigned int *p; + unsigned int sum; +#define MM_INCH 25.4 + int xdpi, ydpi; + + mfhPtr->key = 0x9ac6cdd7L; + mfhPtr->hmf = 0; + mfhPtr->inch = 1440; + + Blt_ScreenDPI(tkwin, &xdpi, &ydpi); + mfhPtr->bbox.Left = mfhPtr->bbox.Top = 0; + mfhPtr->bbox.Bottom = (SHORT)((width * 1440)/ (float)xdpi); + mfhPtr->bbox.Right = (SHORT)((height * 1440) / (float)ydpi); + mfhPtr->reserved = 0; + sum = 0; + for (p = (unsigned int *)mfhPtr; + p < (unsigned int *)&(mfhPtr->checksum); p++) { + sum ^= *p; + } + mfhPtr->checksum = sum; + return TCL_OK; +} + +static int +CreateAPMetaFile(Tcl_Interp *interp, HANDLE hMetaFile, HDC hDC, + APMHEADER *mfhPtr, const char *fileName) +{ + HANDLE hFile; + HANDLE hMem; + LPVOID buffer; + int result; + DWORD count, nBytes; + + result = TCL_ERROR; + hMem = NULL; + hFile = CreateFile( + fileName, /* File path */ + GENERIC_WRITE, /* Access mode */ + 0, /* No sharing. */ + NULL, /* Security attributes */ + CREATE_ALWAYS, /* Overwrite any existing file */ + FILE_ATTRIBUTE_NORMAL, + NULL); /* No template file */ + if (hFile == INVALID_HANDLE_VALUE) { + Tcl_AppendResult(interp, "can't create metafile \"", fileName, + "\":", Blt_LastError(), (char *)NULL); + return TCL_ERROR; + } + if ((!WriteFile(hFile, (LPVOID)mfhPtr, sizeof(APMHEADER), &count, + NULL)) || (count != sizeof(APMHEADER))) { + Tcl_AppendResult(interp, "can't create metafile header to \"", + fileName, "\":", Blt_LastError(), (char *)NULL); + goto error; + } + nBytes = GetWinMetaFileBits(hMetaFile, 0, NULL, MM_ANISOTROPIC, hDC); + hMem = GlobalAlloc(GHND, nBytes); + if (hMem == NULL) { + Tcl_AppendResult(interp, "can't create allocate global memory:", + Blt_LastError(), (char *)NULL); + goto error; + } + buffer = (LPVOID)GlobalLock(hMem); + if (!GetWinMetaFileBits(hMetaFile, nBytes, buffer, MM_ANISOTROPIC, hDC)) { + Tcl_AppendResult(interp, "can't get metafile bits:", + Blt_LastError(), (char *)NULL); + goto error; + } + if ((!WriteFile(hFile, buffer, nBytes, &count, NULL)) || + (count != nBytes)) { + Tcl_AppendResult(interp, "can't write metafile bits:", + Blt_LastError(), (char *)NULL); + goto error; + } + result = TCL_OK; + error: + CloseHandle(hFile); + if (hMem != NULL) { + GlobalUnlock(hMem); + GlobalFree(hMem); + } + return result; +} +#endif /*WIN32*/ + +/* + *--------------------------------------------------------------------------- + * + * SnapOp -- + * + * Snaps a picture of the graph and stores it in the specified image. + * + * Results: + * Returns a standard TCL result. interp->result contains + * the list of the graph coordinates. If an error occurred + * while parsing the window positions, TCL_ERROR is returned, + * then interp->result will contain an error message. + * + *--------------------------------------------------------------------------- + */ +static int +SnapOp(Graph *graphPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) +{ + int result; + Pixmap drawable; + int i; + SnapSwitches switches; + + /* .g snap ?switches? name */ + switches.height = Tk_Height(graphPtr->tkwin); + if ((switches.height < 2) && (graphPtr->reqHeight > 0)) { + switches.height = graphPtr->reqHeight; + } + switches.width = Tk_Width(graphPtr->tkwin); + if ((switches.width < 2) && (graphPtr->reqWidth > 0)) { + switches.width = graphPtr->reqWidth; + } + switches.format = FORMAT_PICTURE; + /* Process switches */ + i = Blt_ParseSwitches(interp, snapSwitches, objc - 2, objv + 2, &switches, + BLT_SWITCH_OBJV_PARTIAL); + if (i < 0) { + return TCL_ERROR; + } + i += 2; + if (i >= objc) { + Tcl_AppendResult(interp, "missing name argument: should be \"", + Tcl_GetString(objv[0]), "snap ?switches? name\"", (char *)NULL); + return TCL_ERROR; + } + switches.name = Tcl_GetString(objv[i]); + if (switches.width < 2) { + switches.width = Tk_ReqWidth(graphPtr->tkwin); + } + if (switches.height < 2) { + switches.width = Tk_ReqHeight(graphPtr->tkwin); + } + /* Always re-compute the layout of the graph before snapping the picture. */ + graphPtr->width = switches.width; + graphPtr->height = switches.height; + Blt_MapGraph(graphPtr); + + drawable = Tk_WindowId(graphPtr->tkwin); + switch (switches.format) { + case FORMAT_PICTURE: + case FORMAT_PHOTO: + drawable = Tk_GetPixmap(graphPtr->display, drawable, graphPtr->width, + graphPtr->height, Tk_Depth(graphPtr->tkwin)); +#ifdef WIN32 + assert(drawable != None); +#endif + graphPtr->flags |= RESET_WORLD; + Blt_DrawGraph(graphPtr, drawable); + if (switches.format == FORMAT_PICTURE) { + result = Blt_SnapPicture(interp, graphPtr->tkwin, drawable, 0, 0, + switches.width, switches.height, switches.width, + switches.height, switches.name, 1.0); + } else { + result = Blt_SnapPhoto(interp, graphPtr->tkwin, drawable, 0, 0, + switches.width, switches.height, switches.width, + switches.height, switches.name, 1.0); + } + Tk_FreePixmap(graphPtr->display, drawable); + break; + +#ifdef WIN32 + case FORMAT_WMF: + case FORMAT_EMF: + { + TkWinDC drawableDC; + TkWinDCState state; + HDC hRefDC, hDC; + HENHMETAFILE hMetaFile; + Tcl_DString dString; + const char *title; + + hRefDC = TkWinGetDrawableDC(graphPtr->display, drawable, &state); + + Tcl_DStringInit(&dString); + Tcl_DStringAppend(&dString, "BLT Graph ", -1); + Tcl_DStringAppend(&dString, BLT_VERSION, -1); + Tcl_DStringAppend(&dString, "\0", -1); + Tcl_DStringAppend(&dString, Tk_PathName(graphPtr->tkwin), -1); + Tcl_DStringAppend(&dString, "\0", -1); + title = Tcl_DStringValue(&dString); + hDC = CreateEnhMetaFile(hRefDC, NULL, NULL, title); + Tcl_DStringFree(&dString); + + if (hDC == NULL) { + Tcl_AppendResult(interp, "can't create metafile: ", + Blt_LastError(), (char *)NULL); + return TCL_ERROR; + } + + drawableDC.hdc = hDC; + drawableDC.type = TWD_WINDC; + + graphPtr->width = switches.width; + graphPtr->height = switches.height; + Blt_MapGraph(graphPtr); + graphPtr->flags |= RESET_WORLD; + Blt_DrawGraph(graphPtr, (Drawable)&drawableDC); + hMetaFile = CloseEnhMetaFile(hDC); + if (strcmp(switches.name, "CLIPBOARD") == 0) { + HWND hWnd; + + hWnd = Tk_GetHWND(drawable); + OpenClipboard(hWnd); + EmptyClipboard(); + SetClipboardData(CF_ENHMETAFILE, hMetaFile); + CloseClipboard(); + result = TCL_OK; + } else { + result = TCL_ERROR; + if (switches.format == FORMAT_WMF) { + APMHEADER mfh; + + assert(sizeof(mfh) == 22); + InitMetaFileHeader(graphPtr->tkwin, switches.width, + switches.height, &mfh); + result = CreateAPMetaFile(interp, hMetaFile, hRefDC, &mfh, + switches.name); + } else { + HENHMETAFILE hMetaFile2; + + hMetaFile2 = CopyEnhMetaFile(hMetaFile, switches.name); + if (hMetaFile2 != NULL) { + result = TCL_OK; + DeleteEnhMetaFile(hMetaFile2); + } + } + DeleteEnhMetaFile(hMetaFile); + } + TkWinReleaseDrawableDC(drawable, hRefDC, &state); + } + break; +#endif /*WIN32*/ + default: + Tcl_AppendResult(interp, "bad snapshot format", (char *)NULL); + return TCL_ERROR; + } + graphPtr->flags |= MAP_WORLD; + Blt_EventuallyRedrawGraph(graphPtr); + return result; +} + +/* + *--------------------------------------------------------------------------- + * + * GraphWidgetCmd -- + * + * This procedure is invoked to process the TCL command that + * corresponds to a widget managed by this module. See the user + * documentation for details on what it does. + * + * Results: + * A standard TCL result. + * + * Side effects: + * See the user documentation. + * + *--------------------------------------------------------------------------- + */ +static Blt_OpSpec graphOps[] = +{ + {"axis", 1, Blt_VirtualAxisOp, 2, 0, "oper ?args?",}, + {"bar", 2, BarOp, 2, 0, "oper ?args?",}, + {"cget", 2, CgetOp, 3, 3, "option",}, + {"configure", 2, ConfigureOp, 2, 0, "?option value?...",}, + {"crosshairs", 2, Blt_CrosshairsOp, 2, 0, "oper ?args?",}, + {"element", 2, ElementOp, 2, 0, "oper ?args?",}, + {"extents", 2, ExtentsOp, 3, 3, "item",}, + {"inside", 3, InsideOp, 4, 4, "winX winY",}, + {"invtransform", 3, InvtransformOp, 4, 4, "winX winY",}, + {"legend", 2, Blt_LegendOp, 2, 0, "oper ?args?",}, + {"line", 2, LineOp, 2, 0, "oper ?args?",}, + {"marker", 2, Blt_MarkerOp, 2, 0, "oper ?args?",}, + {"pen", 2, Blt_PenOp, 2, 0, "oper ?args?",}, + {"postscript", 2, Blt_PostScriptOp, 2, 0, "oper ?args?",}, +#ifndef NO_PRINTER + {"print1", 2, Print1Op, 2, 3, "?printerName?",}, + {"print2", 2, Print2Op, 2, 3, "?printerName?",}, +#endif /*NO_PRINTER*/ + {"snap", 1, SnapOp, 3, 0, "?switches? name",}, + {"transform", 1, TransformOp, 4, 4, "x y",}, + {"x2axis", 2, X2AxisOp, 2, 0, "oper ?args?",}, + {"xaxis", 2, XAxisOp, 2, 0, "oper ?args?",}, + {"y2axis", 2, Y2AxisOp, 2, 0, "oper ?args?",}, + {"yaxis", 2, YAxisOp, 2, 0, "oper ?args?",}, +}; +static int nGraphOps = sizeof(graphOps) / sizeof(Blt_OpSpec); + +int +Blt_GraphInstCmdProc(ClientData clientData, Tcl_Interp *interp, int objc, + Tcl_Obj *const *objv) +{ + GraphCmdProc *proc; + int result; + Graph *graphPtr = clientData; + + proc = Blt_GetOpFromObj(interp, nGraphOps, graphOps, BLT_OP_ARG1, + objc, objv, 0); + if (proc == NULL) { + return TCL_ERROR; + } + Tcl_Preserve(graphPtr); + result = (*proc) (graphPtr, interp, objc, objv); + Tcl_Release(graphPtr); + return result; +} + +/* + *--------------------------------------------------------------------------- + * + * NewGraph -- + * + * Creates a new window and TCL command representing an instance of a + * graph widget. + * + * Results: + * A standard TCL result. + * + * Side effects: + * See the user documentation. + * + *--------------------------------------------------------------------------- + */ +static int +NewGraph(Tcl_Interp *interp, int objc, Tcl_Obj *const *objv, ClassId classId) +{ + Graph *graphPtr; + + if (objc < 2) { + Tcl_AppendResult(interp, "wrong # args: should be \"", + Tcl_GetString(objv[0]), " pathName ?option value?...\"", + (char *)NULL); + return TCL_ERROR; + } + graphPtr = CreateGraph(interp, objc, objv, classId); + if (graphPtr == NULL) { + return TCL_ERROR; + } + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * GraphCmd -- + * + * Creates a new window and TCL command representing an instance of a + * graph widget. + * + * Results: + * A standard TCL result. + * + * Side effects: + * See the user documentation. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +GraphCmd(ClientData clientData, Tcl_Interp *interp, int objc, + Tcl_Obj *const *objv) +{ + return NewGraph(interp, objc, objv, CID_ELEM_LINE); +} + +/* + *--------------------------------------------------------------------------- + * + * BarchartCmd -- + * + * Creates a new window and TCL command representing an instance of a + * barchart widget. + * + * Results: + * A standard TCL result. + * + * Side effects: + * See the user documentation. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +BarchartCmd(ClientData clientData, Tcl_Interp *interp, int objc, + Tcl_Obj *const *objv) +{ + return NewGraph(interp, objc, objv, CID_ELEM_BAR); +} + +/* + *--------------------------------------------------------------------------- + * + * StripchartCmd -- + * + * Creates a new window and TCL command representing an instance of a + * barchart widget. + * + * Results: + * A standard TCL result. + * + * Side effects: + * See the user documentation. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +StripchartCmd(ClientData clientData, Tcl_Interp *interp, int objc, + Tcl_Obj *const *objv) +{ + return NewGraph(interp, objc, objv, CID_ELEM_STRIP); +} + +/* + *--------------------------------------------------------------------------- + * + * DrawMargins -- + * + * Draws the exterior region of the graph (axes, ticks, titles, etc) + * onto a pixmap. The interior region is defined by the given rectangle + * structure. + * + * --------------------------------- + * | | + * | rectArr[0] | + * | | + * --------------------------------- + * | |top right| | + * | | | | + * | | | | + * | [1] | | [2] | + * | | | | + * | | | | + * | | | | + * | | | | + * | | | | + * | |left bottom| | + * --------------------------------- + * | | + * | rectArr[3] | + * | | + * --------------------------------- + * + * X coordinate axis + * Y coordinate axis + * legend + * interior border + * exterior border + * titles (X and Y axis, graph) + * + * Returns: + * None. + * + * Side Effects: + * Exterior of graph is displayed in its window. + * + *--------------------------------------------------------------------------- + */ +static void +DrawMargins(Graph *graphPtr, Drawable drawable) +{ + XRectangle rects[4]; + int site; + + /* + * Draw the four outer rectangles which encompass the plotting + * surface. This clears the surrounding area and clips the plot. + */ + rects[0].x = rects[0].y = rects[3].x = rects[1].x = 0; + rects[0].width = rects[3].width = (short int)graphPtr->width; + rects[0].height = (short int)graphPtr->top; + rects[3].y = graphPtr->bottom; + rects[3].height = graphPtr->height - graphPtr->bottom; + rects[2].y = rects[1].y = graphPtr->top; + rects[1].width = graphPtr->left; + rects[2].height = rects[1].height = graphPtr->bottom - graphPtr->top; + rects[2].x = graphPtr->right; + rects[2].width = graphPtr->width - graphPtr->right; + + Blt_FillBackgroundRectangle(graphPtr->tkwin, drawable, graphPtr->normalBg, + rects[0].x, rects[0].y, rects[0].width, rects[0].height, + 0, TK_RELIEF_FLAT); + Blt_FillBackgroundRectangle(graphPtr->tkwin, drawable, graphPtr->normalBg, + rects[1].x, rects[1].y, rects[1].width, rects[1].height, + 0, TK_RELIEF_FLAT); + Blt_FillBackgroundRectangle(graphPtr->tkwin, drawable, graphPtr->normalBg, + rects[2].x, rects[2].y, rects[2].width, rects[2].height, + 0, TK_RELIEF_FLAT); + Blt_FillBackgroundRectangle(graphPtr->tkwin, drawable, graphPtr->normalBg, + rects[3].x, rects[3].y, rects[3].width, rects[3].height, + 0, TK_RELIEF_FLAT); + + /* Draw 3D border around the plotting area */ + + if (graphPtr->plotBW > 0) { + int x, y, w, h; + + x = graphPtr->left - graphPtr->plotBW; + y = graphPtr->top - graphPtr->plotBW; + w = (graphPtr->right - graphPtr->left) + (2*graphPtr->plotBW); + h = (graphPtr->bottom - graphPtr->top) + (2*graphPtr->plotBW); + Blt_DrawBackgroundRectangle(graphPtr->tkwin, drawable, + graphPtr->normalBg, x, y, w, h, graphPtr->plotBW, + graphPtr->plotRelief); + } + site = Blt_Legend_Site(graphPtr); + if (site & LEGEND_MARGIN_MASK) { + /* Legend is drawn on one of the graph margins */ + Blt_DrawLegend(graphPtr, drawable); + } else if (site == LEGEND_WINDOW) { + Blt_Legend_EventuallyRedraw(graphPtr); + } + if (graphPtr->title != NULL) { + Blt_DrawText(graphPtr->tkwin, drawable, graphPtr->title, + &graphPtr->titleTextStyle, graphPtr->titleX, graphPtr->titleY); + } + Blt_DrawAxes(graphPtr, drawable); + graphPtr->flags &= ~DRAW_MARGINS; +} + +#ifdef notdef +/* + *--------------------------------------------------------------------------- + * + * DrawPlotRegion -- + * + * Draws the contents of the plotting area. This consists of the + * elements, markers (draw under elements), axis limits, and possibly the + * legend. Typically, the output will be cached into a backing store + * pixmap, so that redraws can occur quickly. + * + * Results: + * None. + * + *--------------------------------------------------------------------------- + */ +static void +DrawPlotRegion(Graph *graphPtr, Drawable drawable) +{ + int site; + + /* Clear the background of the plotting area. */ + Blt_FillBackgroundRectangle(graphPtr->tkwin, drawable, graphPtr->plotBg, + graphPtr->left, graphPtr->top, graphPtr->right - graphPtr->left + 1, + graphPtr->bottom - graphPtr->top + 1, TK_RELIEF_FLAT, 0); + + /* Draw the elements, markers, legend, and axis limits. */ + + Blt_DrawGrids(graphPtr, drawable); + Blt_DrawMarkers(graphPtr, drawable, MARKER_UNDER); + site = Blt_Legend_Site(graphPtr); + if ((site & LEGEND_PLOTAREA_MASK) && + (!Blt_Legend_IsRaised(graphPtr))) { + Blt_DrawLegend(graphPtr, drawable); + } else if (site == LEGEND_WINDOW) { + Blt_Legend_EventuallyRedraw(graphPtr); + } + Blt_DrawAxisLimits(graphPtr, drawable); + DrawMargins(graphPtr, drawable); + Blt_DrawElements(graphPtr, drawable); + Blt_DrawAxes(graphPtr, drawable); +} +#endif + +/* + *--------------------------------------------------------------------------- + * + * DrawPlot -- + * + * Draws the contents of the plotting area. This consists of the + * elements, markers (draw under elements), axis limits, and possibly the + * legend. Typically, the output will be cached into a backing store + * pixmap, so that redraws can occur quickly. + * + * Results: + * None. + * + *--------------------------------------------------------------------------- + */ +static void +DrawPlot(Graph *graphPtr, Drawable drawable) +{ + int site; + + DrawMargins(graphPtr, drawable); + + /* Draw the background of the plotting area with 3D border. */ + Blt_FillBackgroundRectangle(graphPtr->tkwin, drawable, graphPtr->plotBg, + graphPtr->left - graphPtr->plotBW, + graphPtr->top - graphPtr->plotBW, + graphPtr->right - graphPtr->left + 1 + 2 * graphPtr->plotBW, + graphPtr->bottom - graphPtr->top + 1 + 2 * graphPtr->plotBW, + graphPtr->plotBW, graphPtr->plotRelief); + + /* Draw the elements, markers, legend, and axis limits. */ + Blt_DrawAxes(graphPtr, drawable); + Blt_DrawGrids(graphPtr, drawable); + Blt_DrawMarkers(graphPtr, drawable, MARKER_UNDER); + + site = Blt_Legend_Site(graphPtr); + if ((site & LEGEND_PLOTAREA_MASK) && (!Blt_Legend_IsRaised(graphPtr))) { + Blt_DrawLegend(graphPtr, drawable); + } else if (site == LEGEND_WINDOW) { + Blt_Legend_EventuallyRedraw(graphPtr); + } + Blt_DrawAxisLimits(graphPtr, drawable); + Blt_DrawElements(graphPtr, drawable); + /* Blt_DrawAxes(graphPtr, drawable); */ +} + +void +Blt_MapGraph(Graph *graphPtr) +{ + if (graphPtr->flags & RESET_AXES) { + Blt_ResetAxes(graphPtr); + } + if (graphPtr->flags & LAYOUT_NEEDED) { + Blt_LayoutGraph(graphPtr); + graphPtr->flags &= ~LAYOUT_NEEDED; + } + /* Compute coordinate transformations for graph components */ + if ((graphPtr->vRange > 1) && (graphPtr->hRange > 1)) { + if (graphPtr->flags & MAP_WORLD) { + Blt_MapAxes(graphPtr); + } + Blt_MapElements(graphPtr); + Blt_MapMarkers(graphPtr); + graphPtr->flags &= ~(MAP_ALL); + } +} + +void +Blt_DrawGraph(Graph *graphPtr, Drawable drawable) +{ + DrawPlot(graphPtr, drawable); + /* Draw markers above elements */ + Blt_DrawMarkers(graphPtr, drawable, MARKER_ABOVE); + Blt_DrawActiveElements(graphPtr, drawable); + + /* Don't draw legend in the plot area. */ + if ((Blt_Legend_Site(graphPtr) & LEGEND_PLOTAREA_MASK) && + (Blt_Legend_IsRaised(graphPtr))) { + Blt_DrawLegend(graphPtr, drawable); + } + /* Draw 3D border just inside of the focus highlight ring. */ + if ((graphPtr->borderWidth > 0) && (graphPtr->relief != TK_RELIEF_FLAT)) { + Blt_DrawBackgroundRectangle(graphPtr->tkwin, drawable, + graphPtr->normalBg, graphPtr->highlightWidth, + graphPtr->highlightWidth, + graphPtr->width - 2 * graphPtr->highlightWidth, + graphPtr->height - 2 * graphPtr->highlightWidth, + graphPtr->borderWidth, graphPtr->relief); + } + /* Draw focus highlight ring. */ + if ((graphPtr->highlightWidth > 0) && (graphPtr->flags & FOCUS)) { + GC gc; + + gc = Tk_GCForColor(graphPtr->highlightColor, drawable); + Tk_DrawFocusHighlight(graphPtr->tkwin, gc, graphPtr->highlightWidth, + drawable); + } +} + +static void +UpdateMarginTraces(Graph *graphPtr) +{ + Margin *marginPtr, *endPtr; + + for (marginPtr = graphPtr->margins, endPtr = marginPtr + 4; + marginPtr < endPtr; marginPtr++) { + if (marginPtr->varName != NULL) { /* Trigger variable traces */ + int size; + + if ((marginPtr->site == MARGIN_LEFT) || + (marginPtr->site == MARGIN_RIGHT)) { + size = marginPtr->width; + } else { + size = marginPtr->height; + } + Tcl_SetVar(graphPtr->interp, marginPtr->varName, Blt_Itoa(size), + TCL_GLOBAL_ONLY); + } + } +} + +/* + *--------------------------------------------------------------------------- + * + * DisplayGraph -- + * + * This procedure is invoked to display a graph widget. + * + * Results: + * None. + * + * Side effects: + * Commands are output to X to display the graph in its current mode. + * + *--------------------------------------------------------------------------- + */ +static void +DisplayGraph(ClientData clientData) +{ + Graph *graphPtr = clientData; + Pixmap drawable; + Tk_Window tkwin; + int site; + + graphPtr->flags &= ~REDRAW_PENDING; + if (graphPtr->tkwin == NULL) { + return; /* Window has been destroyed (we + * should not get here) */ + } + tkwin = graphPtr->tkwin; +#ifdef notdef + fprintf(stderr, "Calling DisplayGraph(%s)\n", Tk_PathName(tkwin)); +#endif + if ((Tk_Width(tkwin) <= 1) || (Tk_Height(tkwin) <= 1)) { + /* Don't bother computing the layout until the size of the window is + * something reasonable. */ + return; + } + graphPtr->width = Tk_Width(tkwin); + graphPtr->height = Tk_Height(tkwin); + Blt_MapGraph(graphPtr); + if (!Tk_IsMapped(tkwin)) { + /* The graph's window isn't displayed, so don't bother drawing + * anything. By getting this far, we've at least computed the + * coordinates of the graph's new layout. */ + return; + } + /* Create a pixmap the size of the window for double buffering. */ + if (graphPtr->doubleBuffer) { + drawable = Tk_GetPixmap(graphPtr->display, Tk_WindowId(tkwin), + graphPtr->width, graphPtr->height, Tk_Depth(tkwin)); + } else { + drawable = Tk_WindowId(tkwin); + } + if (graphPtr->backingStore) { + if ((graphPtr->cache == None) || + (graphPtr->cacheWidth != graphPtr->width) || + (graphPtr->cacheHeight != graphPtr->height)) { + if (graphPtr->cache != None) { + Tk_FreePixmap(graphPtr->display, graphPtr->cache); + } + + + graphPtr->cache = Tk_GetPixmap(graphPtr->display, + Tk_WindowId(tkwin), graphPtr->width, graphPtr->height, + Tk_Depth(tkwin)); + graphPtr->cacheWidth = graphPtr->width; + graphPtr->cacheHeight = graphPtr->height; + graphPtr->flags |= CACHE_DIRTY; + } + } +#ifdef WIN32 + assert(drawable != None); +#endif + if (graphPtr->backingStore) { + if (graphPtr->flags & CACHE_DIRTY) { + /* The backing store is new or out-of-date. */ + DrawPlot(graphPtr, graphPtr->cache); + graphPtr->flags &= ~CACHE_DIRTY; + } + /* Copy the pixmap to the one used for drawing the entire graph. */ + XCopyArea(graphPtr->display, graphPtr->cache, drawable, + graphPtr->drawGC, 0, 0, Tk_Width(graphPtr->tkwin), + Tk_Height(graphPtr->tkwin), 0, 0); + } else { + DrawPlot(graphPtr, drawable); + } + /* Draw markers above elements */ + Blt_DrawMarkers(graphPtr, drawable, MARKER_ABOVE); + Blt_DrawActiveElements(graphPtr, drawable); + /* Don't draw legend in the plot area. */ + site = Blt_Legend_Site(graphPtr); + if ((site & LEGEND_PLOTAREA_MASK) && (Blt_Legend_IsRaised(graphPtr))) { + Blt_DrawLegend(graphPtr, drawable); + } + if (site == LEGEND_WINDOW) { + Blt_Legend_EventuallyRedraw(graphPtr); + } + /* Draw 3D border just inside of the focus highlight ring. */ + if ((graphPtr->borderWidth > 0) && (graphPtr->relief != TK_RELIEF_FLAT)) { + Blt_DrawBackgroundRectangle(graphPtr->tkwin, drawable, + graphPtr->normalBg, graphPtr->highlightWidth, + graphPtr->highlightWidth, + graphPtr->width - 2 * graphPtr->highlightWidth, + graphPtr->height - 2 * graphPtr->highlightWidth, + graphPtr->borderWidth, graphPtr->relief); + } + /* Draw focus highlight ring. */ + if ((graphPtr->highlightWidth > 0) && (graphPtr->flags & FOCUS)) { + GC gc; + + gc = Tk_GCForColor(graphPtr->highlightColor, drawable); + Tk_DrawFocusHighlight(graphPtr->tkwin, gc, graphPtr->highlightWidth, + drawable); + } + /* Disable crosshairs before redisplaying to the screen */ + Blt_DisableCrosshairs(graphPtr); + XCopyArea(graphPtr->display, drawable, Tk_WindowId(tkwin), + graphPtr->drawGC, 0, 0, graphPtr->width, graphPtr->height, 0, 0); + Blt_EnableCrosshairs(graphPtr); + if (graphPtr->doubleBuffer) { + Tk_FreePixmap(graphPtr->display, drawable); + } + graphPtr->flags &= ~RESET_WORLD; + UpdateMarginTraces(graphPtr); +} + +/*LINTLIBRARY*/ +int +Blt_GraphCmdInitProc(Tcl_Interp *interp) +{ + static Blt_InitCmdSpec cmdSpecs[] = { + {"graph", GraphCmd,}, + {"barchart", BarchartCmd,}, + {"stripchart", StripchartCmd,}, + }; + return Blt_InitCmds(interp, "::blt", cmdSpecs, 3); +} + +Graph * +Blt_GetGraphFromWindowData(Tk_Window tkwin) +{ + Graph *graphPtr; + + while (tkwin != NULL) { + graphPtr = (Graph *)Blt_GetWindowInstanceData(tkwin); + if (graphPtr != NULL) { + return graphPtr; + } + tkwin = Tk_Parent(tkwin); + } + return NULL; +} + +int +Blt_GraphType(Graph *graphPtr) +{ + switch (graphPtr->classId) { + case CID_ELEM_LINE: + return GRAPH; + case CID_ELEM_BAR: + return BARCHART; + case CID_ELEM_STRIP: + return STRIPCHART; + default: + return 0; + } + return 0; +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_ReconfigureGraph -- + * + * Allocates resources for the graph. + * + *--------------------------------------------------------------------------- + */ +void +Blt_ReconfigureGraph(Graph *graphPtr) +{ + ConfigureGraph(graphPtr); + Blt_ConfigureLegend(graphPtr); + Blt_ConfigureElements(graphPtr); + Blt_ConfigureAxes(graphPtr); + Blt_ConfigureMarkers(graphPtr); +} diff --git a/src/bltGraph.h b/src/bltGraph.h new file mode 100644 index 0000000..62ab031 --- /dev/null +++ b/src/bltGraph.h @@ -0,0 +1,699 @@ + +/* + * bltGraph.h -- + * + * Copyright 1993-2004 George A Howlett. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef _BLT_GRAPH_H +#define _BLT_GRAPH_H + +#include "bltInt.h" +#include "bltHash.h" +#include "bltBind.h" +#include "bltChain.h" +#include "bltPs.h" +#include "bltBgStyle.h" + +typedef struct _Graph Graph; +typedef struct _Element Element; +typedef struct _Legend Legend; + +typedef enum { + CID_NONE, + CID_AXIS_X, + CID_AXIS_Y, + CID_ELEM_BAR, + CID_ELEM_CONTOUR, + CID_ELEM_LINE, + CID_ELEM_STRIP, + CID_MARKER_BITMAP, + CID_MARKER_IMAGE, + CID_MARKER_LINE, + CID_MARKER_POLYGON, + CID_MARKER_TEXT, + CID_MARKER_WINDOW, + CID_LEGEND_ENTRY, +} ClassId; + +typedef struct { + /* Generic fields common to all graph objects. */ + ClassId classId; /* Class type of object. */ + const char *name; /* Identifier to refer the object. */ + const char *className; /* Class name of object. */ + + Graph *graphPtr; /* Graph containing of the object. */ + + const char **tags; /* Binding tags for the object. */ +} GraphObj; + +#include "bltGrAxis.h" +#include "bltGrLegd.h" + +#define MARKER_UNDER 1 /* Draw markers designated to lie underneath + * elements, grids, legend, etc. */ +#define MARKER_ABOVE 0 /* Draw markers designated to rest above + * elements, grids, legend, etc. */ + +#define PADX 2 /* Padding between labels/titles */ +#define PADY 2 /* Padding between labels */ + +#define MINIMUM_MARGIN 20 /* Minimum margin size */ + + +#define BOUND(x, lo, hi) \ + (((x) > (hi)) ? (hi) : ((x) < (lo)) ? (lo) : (x)) + +/* + *--------------------------------------------------------------------------- + * + * Graph component structure definitions + * + *--------------------------------------------------------------------------- + */ +#define PointInGraph(g,x,y) \ + (((x) <= (g)->right) && ((x) >= (g)->left) && \ + ((y) <= (g)->bottom) && ((y) >= (g)->top)) + +/* + * Mask values used to selectively enable GRAPH or BARCHART entries in the + * various configuration specs. + */ +#define GRAPH (BLT_CONFIG_USER_BIT << 1) +#define STRIPCHART (BLT_CONFIG_USER_BIT << 2) +#define BARCHART (BLT_CONFIG_USER_BIT << 3) +#define LINE_GRAPHS (GRAPH | STRIPCHART) +#define ALL_GRAPHS (GRAPH | BARCHART | STRIPCHART) + +#define ACTIVE_PEN (BLT_CONFIG_USER_BIT << 16) +#define NORMAL_PEN (BLT_CONFIG_USER_BIT << 17) +#define ALL_PENS (NORMAL_PEN | ACTIVE_PEN) + +typedef struct { + Segment2d *segments; + int length; + int *map; +} GraphSegments; + +typedef struct { + Point2d *points; + int length; + int *map; +} GraphPoints; + + +/* + *--------------------------------------------------------------------------- + * + * BarGroup -- + * + * Represents a sets of bars with the same abscissa. This structure is + * used to track the display of the bars in the group. + * + * Each unique abscissa has at least one group. Groups can be + * defined by the bar element's -group option. Multiple groups are + * needed when you are displaying/comparing similar sets of data (same + * abscissas) but belong to a separate group. + * + *--------------------------------------------------------------------------- + */ +typedef struct { + int nSegments; /* Number of occurrences of + * x-coordinate */ + Axis2d axes; /* The axes associated with this + * group. (mapped to the x-value) */ + float sum; /* Sum of the ordinates (y-coorinate) of + * each duplicate abscissa. Used to + * determine height of stacked bars. */ + int count; /* Current number of bars seen. Used to + * position of the next bar in the + * group. */ + float lastY; /* y-cooridinate position of the + * last bar seen. */ + size_t index; /* Order of group in set (an unique + * abscissa may have more than one + * group). */ +} BarGroup; + +/* + *--------------------------------------------------------------------------- + * + * BarSetKey -- + * + * Key for hash table of set of bars. The bar set is defined by + * coordinates with the same abscissa (x-coordinate). + * + *--------------------------------------------------------------------------- + */ +typedef struct { + float value; /* Duplicated abscissa */ + Axis2d axes; /* Axis mapping of element */ +} BarSetKey; + +/* + * BarModes -- + * + * Bar elements are displayed according to their x-y coordinates. If two + * bars have the same abscissa (x-coordinate), the bar segments will be + * drawn according to one of the following modes: + */ + +typedef enum BarModes { + BARS_INFRONT, /* Each successive bar in a group is + * drawn in front of the previous. */ + BARS_STACKED, /* Each successive bar in a group is + * drawn stacked on top of the previous + * bar. */ + BARS_ALIGNED, /* Each successive bar in a group is + * drawn aligned side-by-side to the + * previous from right-to-left. */ + BARS_OVERLAP /* Like "aligned", each successive bar + * in a group is drawn from + * right-to-left. The bars will overlap + * each other by ~50%. */ +} BarMode; + +typedef struct _Pen Pen; +typedef struct _Marker Marker; + +typedef Pen *(PenCreateProc)(void); +typedef int (PenConfigureProc)(Graph *graphPtr, Pen *penPtr); +typedef void (PenDestroyProc)(Graph *graphPtr, Pen *penPtr); + +struct _Pen { + const char *name; /* Pen style identifier. If NULL pen + * was statically allocated. */ + ClassId classId; /* Type element using this pen. */ + const char *typeId; /* String token identifying the type of + * pen. */ + unsigned int flags; /* Indicates if the pen element is + * active or normal. */ + int refCount; /* Reference count for elements using + * this pen. */ + Blt_HashEntry *hashPtr; + + Blt_ConfigSpec *configSpecs; /* Configuration specifications */ + + PenConfigureProc *configProc; + PenDestroyProc *destroyProc; + Graph *graphPtr; /* Graph that the pen is associated + * with. */ +}; + +/* + *--------------------------------------------------------------------------- + * + * Crosshairs + * + * Contains the line segments positions and graphics context used to + * simulate crosshairs (by XOR-ing) on the graph. + * + *--------------------------------------------------------------------------- + */ +typedef struct _Crosshairs Crosshairs; + +typedef struct { + short int width, height; /* Dimensions of the margin */ + short int axesOffset; + short int axesTitleLength; /* Width of the widest title to be + * shown. Multiple titles are displayed + * in another margin. This is the + * minimum space requirement. */ + short int maxTickWidth; + short int maxTickHeight; + unsigned int nAxes; /* # of axes to be displayed */ + Blt_Chain axes; /* Axes associated with this margin */ + const char *varName; /* If non-NULL, name of variable to be + * updated when the margin size + * changes */ + int reqSize; /* Requested size of margin */ + int site; /* Indicates where margin is located: + * left, right, top, or bottom. */ +} Margin; + +#define MARGIN_NONE -1 +#define MARGIN_BOTTOM 0 /* x */ +#define MARGIN_LEFT 1 /* y */ +#define MARGIN_TOP 2 /* x2 */ +#define MARGIN_RIGHT 3 /* y2 */ + +#define rightMargin margins[MARGIN_RIGHT] +#define leftMargin margins[MARGIN_LEFT] +#define topMargin margins[MARGIN_TOP] +#define bottomMargin margins[MARGIN_BOTTOM] + +/* + *--------------------------------------------------------------------------- + * + * Graph -- + * + * Top level structure containing everything pertaining to the graph. + * + *--------------------------------------------------------------------------- + */ +struct _Graph { + unsigned int flags; /* Flags; see below for definitions. */ + Tcl_Interp *interp; /* Interpreter associated with graph */ + Tk_Window tkwin; /* Window that embodies the graph. + * NULL means that the window has been + * destroyed but the data structures + * haven't yet been cleaned up. */ + Display *display; /* Display containing widget; used to + * release resources after tkwin has + * already gone away. */ + Tcl_Command cmdToken; /* Token for graph's widget command. */ + const char *data; /* This value isn't used in C code. + * It may be used in TCL bindings to + * associate extra data. */ + Tk_Cursor cursor; + int inset; /* Sum of focus highlight and 3-D + * border. Indicates how far to + * offset the graph from outside edge + * of the window. */ + int borderWidth; /* Width of the exterior border */ + int relief; /* Relief of the exterior border. */ + Blt_Background normalBg; /* 3-D border used to delineate the + * plot surface and outer edge of + * window. */ + int highlightWidth; /* Width in pixels of highlight to + * draw around widget when it has the + * focus. <= 0 means don't draw a + * highlight. */ + XColor *highlightBgColor; /* Color for drawing traversal + * highlight area when highlight is + * off. */ + XColor *highlightColor; /* Color for drawing traversal + * highlight. */ + const char *title; /* Graph title */ + short int titleX, titleY; /* Position of title on graph. */ + short int titleWidth, titleHeight; /* Dimensions of title. */ + TextStyle titleTextStyle; /* Title attributes: font, color, + * etc.*/ + + const char *takeFocus; /* Not used in C code, indicates if + * widget should be included in focus + * traversal. */ + Axis *focusPtr; /* The axis that currently has focus. */ + + int reqWidth, reqHeight; /* Requested size of graph window */ + int reqPlotWidth, reqPlotHeight; /* Requested size of plot area. Zero + * means to adjust the dimension + * according to the available space + * left in the window. */ + int width, height; /* Actual size (in pixels) of graph + * window or PostScript page. */ + Blt_HashTable penTable; /* Table of pens */ + struct Component { + Blt_HashTable table; /* Hash table of ids. */ + Blt_Chain displayList; /* Display list. */ + Blt_HashTable tagTable; /* Table of bind tags. */ + } elements, markers, axes; + + Blt_HashTable dataTables; /* Hash table of datatable clients. */ + ClassId classId; /* Default element type */ + Blt_BindTable bindTable; + int nextMarkerId; /* Tracks next marker identifier + * available */ + Blt_Chain axisChain[4]; /* Chain of axes for each of the + * margins. They're separate from the + * margin structures to make it easier + * to invert the X-Y axes by simply + * switching chain pointers. */ + Margin margins[4]; + PageSetup *pageSetup; /* Page layout options: see bltGrPS.c */ + Legend *legend; /* Legend information: see + * bltGrLegd.c */ + Crosshairs *crosshairs; /* Crosshairs information: see + * bltGrHairs.c */ + int halo; /* Maximum distance allowed between + * points when searching for a point */ + int inverted; /* If non-zero, indicates the x and y + * axis positions should be inverted. */ + int stackAxes; /* If non-zero, indicates to stack + * mulitple axes in a margin, rather + * than layering them one on top of + * another. */ + GC drawGC; /* GC for drawing on the margins. This + * includes the axis lines */ + int plotBW; /* Width of interior 3-D border. */ + int plotRelief; /* 3-d effect: TK_RELIEF_RAISED etc. */ + Blt_Background plotBg; /* Color of plotting surface */ + + /* If non-zero, force plot to conform to aspect ratio W/H */ + float aspect; + + short int left, right; /* Coordinates of plot bbox */ + short int top, bottom; + + Blt_Pad xPad; /* Vertical padding for plotarea */ + int vRange, vOffset; /* Vertical axis range and offset from + * the left side of the graph + * window. Used to transform coordinates + * to vertical axes. */ + Blt_Pad yPad; /* Horizontal padding for plotarea */ + int hRange, hOffset; /* Horizontal axis range and offset from + * the top of the graph window. Used to + * transform horizontal axes */ + float vScale, hScale; + + int doubleBuffer; /* If non-zero, draw the graph into a + * pixmap first to reduce flashing. */ + int backingStore; /* If non-zero, cache elements by + * drawing them into a pixmap */ + Pixmap cache; /* Pixmap used to cache elements + * displayed. If *backingStore* is + * non-zero, each element is drawn into + * this pixmap before it is copied onto + * the screen. The pixmap then acts as + * a cache (only the pixmap is + * redisplayed if the none of elements + * have changed). This is done so that + * markers can be redrawn quickly over + * elements without redrawing each + * element. */ + short int cacheWidth, cacheHeight; /* Size of element backing store + * pixmap. */ + + /* + * barchart specific information + */ + float baseline; /* Baseline from bar chart. */ + float barWidth; /* Default width of each bar in graph + * units. The default width is 1.0 + * units. */ + BarMode mode; /* Mode describing how to display bars + * with the same x-coordinates. Mode can + * be "stacked", "aligned", "overlap", + * or "infront" */ + BarGroup *barGroups; /* Contains information about duplicate + * x-values in bar elements (malloc-ed). + * This information can also be accessed + * by the group hash table */ + int nBarGroups; /* # of entries in barGroups array. If + * zero, indicates nothing special + * needs to be * done for "stack" or + * "align" modes */ + Blt_HashTable setTable; /* Table managing sets of bars with + * the same abscissas. The bars in a + * set may be displayed is various + * ways: aligned, overlap, infront, or + * stacked. */ + int maxBarSetSize; + const char *dataCmd; /* New data callback? */ + +}; + +/* + * Bit flags definitions: + * + * All kinds of state information kept here. All these things happen + * when the window is available to draw into (DisplayGraph). Need the + * window width and height before we can calculate graph layout (i.e. the + * screen coordinates of the axes, elements, titles, etc). But we want to + * do this only when we have to, not every time the graph is redrawn. + * + * Same goes for maintaining a pixmap to double buffer graph elements. + * Need to mark when the pixmap needs to updated. + * + * + * MAP_ITEM Indicates that the element/marker/axis + * configuration has changed such that + * its layout of the item (i.e. its + * position in the graph window) needs + * to be recalculated. + * + * MAP_ALL Indicates that the layout of the axes and + * all elements and markers and the graph need + * to be recalculated. Otherwise, the layout + * of only those markers and elements that + * have changed will be reset. + * + * GET_AXIS_GEOMETRY Indicates that the size of the axes needs + * to be recalculated. + * + * RESET_AXES Flag to call to Blt_ResetAxes routine. + * This routine recalculates the scale offset + * (used for mapping coordinates) of each axis. + * If an axis limit has changed, then it sets + * flags to re-layout and redraw the entire + * graph. This needs to happend before the axis + * can compute transformations between graph and + * screen coordinates. + * + * LAYOUT_NEEDED + * + * CACHE_DIRTY If set, redraw all elements into the pixmap + * used for buffering elements. + * + * REDRAW_PENDING Non-zero means a DoWhenIdle handler has + * already been queued to redraw this window. + * + * DRAW_LEGEND Non-zero means redraw the legend. If this is + * the only DRAW_* flag, the legend display + * routine is called instead of the graph + * display routine. + * + * DRAW_MARGINS Indicates that the margins bordering + * the plotting area need to be redrawn. + * The possible reasons are: + * + * 1) an axis configuration changed + * 2) an axis limit changed + * 3) titles have changed + * 4) window was resized. + * + * GRAPH_FOCUS + */ + +#define HIDE (1<<0) /* 0x0001 */ +#define DELETE_PENDING (1<<1) /* 0x0002 */ +#define REDRAW_PENDING (1<<2) /* 0x0004 */ +#define ACTIVE_PENDING (1<<3) /* 0x0008 */ +#define MAP_ITEM (1<<4) /* 0x0010 */ +#define DIRTY (1<<5) /* 0x0020 */ +#define ACTIVE (1<<6) /* 0x0040 */ +#define FOCUS (1<<7) /* 0x0080 */ + +#define MAP_ALL (1<<8) /* 0x0100 */ +#define LAYOUT_NEEDED (1<<9) /* 0x0200 */ +#define RESET_AXES (1<<10)/* 0x0400 */ +#define GET_AXIS_GEOMETRY (1<<11)/* 0x0800 */ + +#define DRAW_LEGEND (1<<12)/* 0x1000 */ +#define DRAW_MARGINS (1<<13)/* 0x2000 */ +#define CACHE_DIRTY (1<<14)/* 0x4000 */ +#define REQ_BACKING_STORE (1<<15)/* 0x8000 */ +#define UNMAP_HIDDEN (1<<16) + +#define MAP_WORLD (MAP_ALL|RESET_AXES|GET_AXIS_GEOMETRY) +#define REDRAW_WORLD (DRAW_LEGEND) +#define RESET_WORLD (REDRAW_WORLD | MAP_WORLD) + +/* + * ---------------------- Forward declarations ------------------------ + */ + +BLT_EXTERN int Blt_CreatePageSetup(Graph *graphPtr); + +BLT_EXTERN int Blt_CreateCrosshairs(Graph *graphPtr); + +BLT_EXTERN double Blt_InvHMap(Axis *axisPtr, double x); + +BLT_EXTERN double Blt_InvVMap(Axis *axisPtr, double x); + +BLT_EXTERN double Blt_HMap(Axis *axisPtr, double x); + +BLT_EXTERN double Blt_VMap(Axis *axisPtr, double y); + +BLT_EXTERN Point2d Blt_InvMap2D(Graph *graphPtr, double x, double y, + Axis2d *pairPtr); + +BLT_EXTERN Point2d Blt_Map2D(Graph *graphPtr, double x, double y, + Axis2d *pairPtr); + +BLT_EXTERN Graph *Blt_GetGraphFromWindowData(Tk_Window tkwin); + +BLT_EXTERN void Blt_AdjustAxisPointers(Graph *graphPtr); + +BLT_EXTERN int Blt_PolyRectClip(Region2d *extsPtr, Point2d *inputPts, + int nInputPts, Point2d *outputPts); + +BLT_EXTERN void Blt_ComputeBarStacks(Graph *graphPtr); + +BLT_EXTERN void Blt_ConfigureCrosshairs(Graph *graphPtr); +BLT_EXTERN void Blt_ConfigureLegend(Graph *graphPtr); +BLT_EXTERN void Blt_ConfigureElements(Graph *graphPtr); +BLT_EXTERN void Blt_ConfigureAxes(Graph *graphPtr); +BLT_EXTERN void Blt_ConfigureMarkers(Graph *graphPtr); +BLT_EXTERN void Blt_ReconfigureGraph(Graph *graphPtr); + +BLT_EXTERN void Blt_DestroyAxes(Graph *graphPtr); + +BLT_EXTERN void Blt_DestroyCrosshairs(Graph *graphPtr); + +BLT_EXTERN void Blt_DestroyElements(Graph *graphPtr); + +BLT_EXTERN void Blt_DestroyMarkers(Graph *graphPtr); + +BLT_EXTERN void Blt_DestroyPageSetup(Graph *graphPtr); + +BLT_EXTERN void Blt_DrawAxes(Graph *graphPtr, Drawable drawable); + +BLT_EXTERN void Blt_DrawAxisLimits(Graph *graphPtr, Drawable drawable); + +BLT_EXTERN void Blt_DrawElements(Graph *graphPtr, Drawable drawable); + +BLT_EXTERN void Blt_DrawActiveElements(Graph *graphPtr, Drawable drawable); + +BLT_EXTERN void Blt_DrawGraph(Graph *graphPtr, Drawable drawable); + +BLT_EXTERN void Blt_DrawMarkers(Graph *graphPtr, Drawable drawable, int under); + +BLT_EXTERN void Blt_Draw2DSegments(Display *display, Drawable drawable, GC gc, + Segment2d *segments, int nSegments); + +BLT_EXTERN int Blt_GetCoordinate(Tcl_Interp *interp, const char *string, + double *valuePtr); + +BLT_EXTERN void Blt_InitBarSetTable(Graph *graphPtr); + +BLT_EXTERN void Blt_LayoutGraph(Graph *graphPtr); + +BLT_EXTERN void Blt_EventuallyRedrawGraph(Graph *graphPtr); + +BLT_EXTERN void Blt_ResetAxes(Graph *graphPtr); + +BLT_EXTERN void Blt_ResetBarGroups(Graph *graphPtr); + +BLT_EXTERN void Blt_GraphExtents(Graph *graphPtr, Region2d *extsPtr); + +BLT_EXTERN void Blt_DisableCrosshairs(Graph *graphPtr); + +BLT_EXTERN void Blt_EnableCrosshairs(Graph *graphPtr); + +BLT_EXTERN void Blt_MapGraph(Graph *graphPtr); + +BLT_EXTERN void Blt_MapAxes(Graph *graphPtr); + +BLT_EXTERN void Blt_MapElements(Graph *graphPtr); + +BLT_EXTERN void Blt_MapMarkers(Graph *graphPtr); + +BLT_EXTERN void Blt_UpdateCrosshairs(Graph *graphPtr); + +BLT_EXTERN void Blt_DestroyPens(Graph *graphPtr); + +BLT_EXTERN int Blt_GetPenFromObj(Tcl_Interp *interp, Graph *graphPtr, + Tcl_Obj *objPtr, ClassId classId, Pen **penPtrPtr); + +BLT_EXTERN Pen *Blt_BarPen(const char *penName); + +BLT_EXTERN Pen *Blt_LinePen(const char *penName); + +BLT_EXTERN Pen *Blt_CreatePen(Graph *graphPtr, const char *penName, + ClassId classId, int objc, Tcl_Obj *const *objv); + +BLT_EXTERN int Blt_InitLinePens(Graph *graphPtr); + +BLT_EXTERN int Blt_InitBarPens(Graph *graphPtr); + +BLT_EXTERN void Blt_FreePen(Pen *penPtr); + +BLT_EXTERN int Blt_VirtualAxisOp(Graph *graphPtr, Tcl_Interp *interp, + int objc, Tcl_Obj *const *objv); + +BLT_EXTERN int Blt_AxisOp(Tcl_Interp *interp, Graph *graphPtr, int margin, + int objc, Tcl_Obj *const *objv); + +BLT_EXTERN int Blt_ElementOp(Graph *graphPtr, Tcl_Interp *interp, int objc, + Tcl_Obj *const *objv, ClassId classId); + +BLT_EXTERN int Blt_CrosshairsOp(Graph *graphPtr, Tcl_Interp *interp, int objc, + Tcl_Obj *const *objv); + +BLT_EXTERN int Blt_MarkerOp(Graph *graphPtr, Tcl_Interp *interp, int objc, + Tcl_Obj *const *objv); + +BLT_EXTERN int Blt_PenOp(Graph *graphPtr, Tcl_Interp *interp, int objc, + Tcl_Obj *const *objv); + +BLT_EXTERN int Blt_PointInPolygon(Point2d *samplePtr, Point2d *screenPts, + int nScreenPts); + +BLT_EXTERN int Blt_RegionInPolygon(Region2d *extsPtr, Point2d *points, + int nPoints, int enclosed); + +BLT_EXTERN int Blt_PointInSegments(Point2d *samplePtr, Segment2d *segments, + int nSegments, double halo); + +BLT_EXTERN int Blt_PostScriptOp(Graph *graphPtr, Tcl_Interp *interp, int objc, + Tcl_Obj *const *objv); + +BLT_EXTERN int Blt_GraphUpdateNeeded(Graph *graphPtr); + +BLT_EXTERN int Blt_DefaultAxes(Graph *graphPtr); + +BLT_EXTERN Axis *Blt_GetFirstAxis(Blt_Chain chain); + +BLT_EXTERN void Blt_UpdateAxisBackgrounds(Graph *graphPtr); + +BLT_EXTERN Marker *Blt_NearestMarker(Graph *graphPtr, int x, int y, int under); + +BLT_EXTERN Axis *Blt_NearestAxis(Graph *graphPtr, int x, int y); + +typedef ClientData (MakeTagProc)(Graph *graphPtr, const char *tagName); + +BLT_EXTERN MakeTagProc Blt_MakeElementTag; +BLT_EXTERN MakeTagProc Blt_MakeMarkerTag; +BLT_EXTERN MakeTagProc Blt_MakeAxisTag; +BLT_EXTERN Blt_BindTagProc Blt_GraphTags; +BLT_EXTERN Blt_BindTagProc Blt_AxisTags; + +BLT_EXTERN int Blt_GraphType(Graph *graphPtr); + +BLT_EXTERN void Blt_UpdateGraph(ClientData clientData); + +BLT_EXTERN void Blt_GraphSetObjectClass(GraphObj *graphObjPtr,ClassId classId); + +BLT_EXTERN void Blt_MarkersToPostScript(Graph *graphPtr, Blt_Ps ps, int under); +BLT_EXTERN void Blt_ElementsToPostScript(Graph *graphPtr, Blt_Ps ps); +BLT_EXTERN void Blt_ActiveElementsToPostScript(Graph *graphPtr, Blt_Ps ps); +BLT_EXTERN void Blt_LegendToPostScript(Graph *graphPtr, Blt_Ps ps); +BLT_EXTERN void Blt_AxesToPostScript(Graph *graphPtr, Blt_Ps ps); +BLT_EXTERN void Blt_AxisLimitsToPostScript(Graph *graphPtr, Blt_Ps ps); + +BLT_EXTERN Element *Blt_LineElement(Graph *graphPtr, const char *name, + ClassId classId); +BLT_EXTERN Element *Blt_BarElement(Graph *graphPtr, const char *name, + ClassId classId); + +BLT_EXTERN void Blt_DrawGrids(Graph *graphPtr, Drawable drawable); + +BLT_EXTERN void Blt_GridsToPostScript(Graph *graphPtr, Blt_Ps ps); +BLT_EXTERN void Blt_InitBarSetTable(Graph *graphPtr); +BLT_EXTERN void Blt_DestroyBarSets(Graph *graphPtr); + +/* ---------------------- Global declarations ------------------------ */ + +BLT_EXTERN const char *Blt_GraphClassName(ClassId classId); + +#endif /* _BLT_GRAPH_H */ diff --git a/src/bltInt.C b/src/bltInt.C new file mode 100644 index 0000000..ac4640c --- /dev/null +++ b/src/bltInt.C @@ -0,0 +1,375 @@ + +/* + * bltCoreInit.c -- + * + * This module initials the non-Tk command of the BLT toolkit, registering the + * commands with the TCL interpreter. + * + * Copyright 1991-2004 George A Howlett. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include "bltInt.h" +#include "bltNsUtil.h" +#include "bltArrayObj.h" +#include "bltMath.h" + +#ifndef BLT_LIBRARY +# ifdef WIN32 +# define BLT_LIBRARY "c:/Program Files/Tcl/lib/blt"##BLT_VERSION +# else +# define BLT_LIBRARY "unknown" +# endif +#endif + +#if (_TCL_VERSION >= _VERSION(8,5,0)) +#define TCL_VERSION_LOADED TCL_PATCH_LEVEL +#else +#define TCL_VERSION_LOADED TCL_VERSION +#endif + +static double bltNaN; + +BLT_EXTERN Tcl_AppInitProc Blt_core_Init; +BLT_EXTERN Tcl_AppInitProc Blt_core_SafeInit; + +static Tcl_MathProc MinMathProc, MaxMathProc; +static char libPath[1024] = +{ + BLT_LIBRARY +}; + +/* + * Script to set the BLT library path in the variable global "blt_library" + * + * Checks the usual locations for a file (bltGraph.pro) from the BLT library. + * The places searched in order are + * + * $BLT_LIBRARY + * $BLT_LIBRARY/blt2.4 + * $BLT_LIBRARY/.. + * $BLT_LIBRARY/../blt2.4 + * $blt_libPath + * $blt_libPath/blt2.4 + * $blt_libPath/.. + * $blt_libPath/../blt2.4 + * $tcl_library + * $tcl_library/blt2.4 + * $tcl_library/.. + * $tcl_library/../blt2.4 + * $env(TCL_LIBRARY) + * $env(TCL_LIBRARY)/blt2.4 + * $env(TCL_LIBRARY)/.. + * $env(TCL_LIBRARY)/../blt2.4 + * + * The TCL variable "blt_library" is set to the discovered path. If the file + * wasn't found, no error is returned. The actual usage of $blt_library is + * purposely deferred so that it can be set from within a script. + */ + +/* FIXME: Change this to a namespace procedure in 3.0 */ + +static char initScript[] = +{"\n\ +global blt_library blt_libPath blt_version tcl_library env\n\ +set blt_library {}\n\ +set path {}\n\ +foreach var { env(BLT_LIBRARY) blt_libPath tcl_library env(TCL_LIBRARY) } { \n\ + if { ![info exists $var] } { \n\ + continue \n\ + } \n\ + set path [set $var] \n\ + if { [file readable [file join $path bltGraph.pro]] } { \n\ + set blt_library $path\n\ + break \n\ + } \n\ + set path [file join $path blt$blt_version ] \n\ + if { [file readable [file join $path bltGraph.pro]] } { \n\ + set blt_library $path\n\ + break \n\ + } \n\ + set path [file dirname [set $var]] \n\ + if { [file readable [file join $path bltGraph.pro]] } { \n\ + set blt_library $path\n\ + break \n\ + } \n\ + set path [file join $path blt$blt_version ] \n\ + if { [file readable [file join $path bltGraph.pro]] } { \n\ + set blt_library $path\n\ + break \n\ + } \n\ +} \n\ +if { $blt_library != \"\" } { \n\ + global auto_path \n\ + lappend auto_path $blt_library \n\ +}\n\ +unset var path\n\ +\n" +}; + + +static Tcl_AppInitProc *cmdProcs[] = +{ +#ifndef NO_BASE64 + Blt_Base64CmdInitProc, +#endif +#ifndef NO_BGEXEC + Blt_BgexecCmdInitProc, +#endif +#ifndef NO_PTYEXEC + Blt_PtyExecCmdInitProc, +#endif +#ifndef NO_CRC32 + Blt_Crc32CmdInitProc, +#endif +#ifndef NO_CSV + Blt_CsvCmdInitProc, +#endif +#ifndef NO_DATATABLE + Blt_TableCmdInitProc, +#endif +#ifndef NO_DDE + Blt_DdeCmdInitProc, +#endif +#ifndef NO_DEBUG + Blt_DebugCmdInitProc, +#endif +#ifndef NO_SPLINE + Blt_SplineCmdInitProc, +#endif +#ifndef NO_TREE + Blt_TreeCmdInitProc, +#endif +#ifndef NO_VECTOR + Blt_VectorCmdInitProc, +#endif +#ifndef NO_WATCH + Blt_WatchCmdInitProc, +#endif + (Tcl_AppInitProc *) NULL +}; + +double +Blt_NaN(void) +{ + return bltNaN; +} + +static double +MakeNaN(void) +{ + union DoubleValue { + unsigned int words[2]; + double value; + } result; + +#ifdef WORDS_BIGENDIAN + result.words[0] = 0x7ff80000; + result.words[1] = 0x00000000; +#else + result.words[0] = 0x00000000; + result.words[1] = 0x7ff80000; +#endif + return result.value; +} + +/* ARGSUSED */ +static int +MinMathProc( + ClientData clientData, /* Not used. */ + Tcl_Interp *interp, /* Not used. */ + Tcl_Value *argsPtr, + Tcl_Value *resultPtr) +{ + Tcl_Value *op1Ptr, *op2Ptr; + + op1Ptr = argsPtr, op2Ptr = argsPtr + 1; + if ((op1Ptr->type == TCL_INT) && (op2Ptr->type == TCL_INT)) { + resultPtr->intValue = MIN(op1Ptr->intValue, op2Ptr->intValue); + resultPtr->type = TCL_INT; + } else { + double a, b; + + a = (op1Ptr->type == TCL_INT) + ? (double)op1Ptr->intValue : op1Ptr->doubleValue; + b = (op2Ptr->type == TCL_INT) + ? (double)op2Ptr->intValue : op2Ptr->doubleValue; + resultPtr->doubleValue = MIN(a, b); + resultPtr->type = TCL_DOUBLE; + } + return TCL_OK; +} + +/*ARGSUSED*/ +static int +MaxMathProc( + ClientData clientData, /* Not used. */ + Tcl_Interp *interp, /* Not used. */ + Tcl_Value *argsPtr, + Tcl_Value *resultPtr) +{ + Tcl_Value *op1Ptr, *op2Ptr; + + op1Ptr = argsPtr, op2Ptr = argsPtr + 1; + if ((op1Ptr->type == TCL_INT) && (op2Ptr->type == TCL_INT)) { + resultPtr->intValue = MAX(op1Ptr->intValue, op2Ptr->intValue); + resultPtr->type = TCL_INT; + } else { + double a, b; + + a = (op1Ptr->type == TCL_INT) + ? (double)op1Ptr->intValue : op1Ptr->doubleValue; + b = (op2Ptr->type == TCL_INT) + ? (double)op2Ptr->intValue : op2Ptr->doubleValue; + resultPtr->doubleValue = MAX(a, b); + resultPtr->type = TCL_DOUBLE; + } + return TCL_OK; +} + +static int +SetLibraryPath(Tcl_Interp *interp) +{ + Tcl_DString dString; + const char *value; + + Tcl_DStringInit(&dString); + Tcl_DStringAppend(&dString, libPath, -1); +#ifdef WIN32 + { + HKEY key; + DWORD result; +# ifndef BLT_REGISTRY_KEY +# define BLT_REGISTRY_KEY "Software\\BLT\\" BLT_VERSION "\\" TCL_VERSION +# endif + result = RegOpenKeyEx( + HKEY_LOCAL_MACHINE, /* Parent key. */ + BLT_REGISTRY_KEY, /* Path to sub-key. */ + 0, /* Reserved. */ + KEY_READ, /* Security access mask. */ + &key); /* Resulting key.*/ + + if (result == ERROR_SUCCESS) { + DWORD size; + + /* Query once to get the size of the string needed */ + result = RegQueryValueEx(key, "BLT_LIBRARY", NULL, NULL, NULL, + &size); + if (result == ERROR_SUCCESS) { + Tcl_DStringSetLength(&dString, size); + /* And again to collect the string. */ + RegQueryValueEx(key, "BLT_LIBRARY", NULL, NULL, + (LPBYTE)Tcl_DStringValue(&dString), &size); + RegCloseKey(key); + } + } + } +#endif /* WIN32 */ + value = Tcl_SetVar(interp, "blt_libPath", Tcl_DStringValue(&dString), + TCL_GLOBAL_ONLY | TCL_LEAVE_ERR_MSG); + Tcl_DStringFree(&dString); + if (value == NULL) { + return TCL_ERROR; + } + return TCL_OK; +} + + +/*LINTLIBRARY*/ +int +Blt_core_Init(Tcl_Interp *interp) /* Interpreter to add extra commands */ +{ + Tcl_AppInitProc **p; + Tcl_Namespace *nsPtr; + Tcl_ValueType args[2]; + const char *result; + const int isExact = 1; + +#ifdef USE_TCL_STUBS + if (Tcl_InitStubs(interp, TCL_VERSION_LOADED, isExact) == NULL) { + return TCL_ERROR; + }; +#endif + Blt_AllocInit(NULL, NULL, NULL); + + /* + * Check that the versions of TCL that have been loaded are the same ones + * that BLT was compiled against. + */ + if (Tcl_PkgRequire(interp, "Tcl", TCL_VERSION_LOADED, isExact) == NULL) { + return TCL_ERROR; + } + + /* Set the "blt_version", "blt_patchLevel", and "blt_libPath" Tcl + * variables. We'll use them in the following script. */ + + result = Tcl_SetVar(interp, "blt_version", BLT_VERSION, TCL_GLOBAL_ONLY); + if (result == NULL) { + return TCL_ERROR; + } + result = Tcl_SetVar(interp, "blt_patchLevel", BLT_PATCH_LEVEL, + TCL_GLOBAL_ONLY); + if (result == NULL) { + return TCL_ERROR; + } + if (SetLibraryPath(interp) != TCL_OK) { + return TCL_ERROR; + } + if (Tcl_Eval(interp, initScript) != TCL_OK) { + return TCL_ERROR; + } + + + nsPtr = Tcl_FindNamespace(interp, "::blt", (Tcl_Namespace *)NULL, 0); + if (nsPtr == NULL) { + nsPtr = Tcl_CreateNamespace(interp, "::blt", NULL, NULL); + if (nsPtr == NULL) { + return TCL_ERROR; + } + } + /* Initialize the BLT commands that only require Tcl. */ + for (p = cmdProcs; *p != NULL; p++) { + if ((**p) (interp) != TCL_OK) { + Tcl_DeleteNamespace(nsPtr); + return TCL_ERROR; + } + } + args[0] = args[1] = TCL_EITHER; + Tcl_CreateMathFunc(interp, "min", 2, args, MinMathProc, (ClientData)0); + Tcl_CreateMathFunc(interp, "max", 2, args, MaxMathProc, (ClientData)0); + Blt_RegisterArrayObj(); + bltNaN = MakeNaN(); + if (Tcl_PkgProvide(interp, "blt_core", BLT_VERSION) != TCL_OK) { + return TCL_ERROR; + } + return TCL_OK; +} + +/*LINTLIBRARY*/ +int +Blt_core_SafeInit(Tcl_Interp *interp) /* Interpreter to add extra commands */ +{ + return Blt_core_Init(interp); +} + +#ifdef USE_DLL +# include "bltWinDll.c" +#endif diff --git a/src/bltNsUtil.C b/src/bltNsUtil.C new file mode 100644 index 0000000..62f727d --- /dev/null +++ b/src/bltNsUtil.C @@ -0,0 +1,174 @@ + +/* + * bltNsUtil.c -- + * + * This module implements utility namespace procedures for the BLT toolkit. + * + * Copyright 1997-2008 George A Howlett. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include "bltInt.h" +#include "bltNsUtil.h" + +/* + * A Command structure exists for each command in a namespace. The Tcl_Command + * opaque type actually refers to these structures. + */ + +typedef struct CompileProc CompileProc; +typedef struct ImportRef ImportRef; +typedef struct CommandTrace CommandTrace; + +typedef struct { + Tcl_HashEntry *hPtr; /* Pointer to the hash table entry that refers + * to this command. The hash table is either a + * namespace's command table or an + * interpreter's hidden command table. This + * pointer is used to get a command's name + * from its Tcl_Command handle. NULL means + * that the hash table entry has been removed + * already (this can happen if deleteProc + * causes the command to be deleted or + * recreated). */ + Tcl_Namespace *nsPtr; /* Points to the namespace containing this + * command. */ + int refCount; /* 1 if in command hashtable plus 1 for each + * reference from a CmdName TCL object + * representing a command's name in a ByteCode + * instruction sequence. This structure can be + * freed when refCount becomes zero. */ + int cmdEpoch; /* Incremented to invalidate any references + * that point to this command when it is + * renamed, deleted, hidden, or exposed. */ + CompileProc *compileProc; /* Procedure called to compile command. NULL + * if no compile proc exists for command. */ + Tcl_ObjCmdProc *objProc; /* Object-based command procedure. */ + ClientData objClientData; /* Arbitrary value passed to object proc. */ + Tcl_CmdProc *proc; /* String-based command procedure. */ + ClientData clientData; /* Arbitrary value passed to string proc. */ + Tcl_CmdDeleteProc *deleteProc; + /* Procedure invoked when deleting command + * to, e.g., free all client data. */ + ClientData deleteData; /* Arbitrary value passed to deleteProc. */ + int flags; /* Means that the command is in the process of + * being deleted (its deleteProc is currently + * executing). Other attempts to delete the + * command should be ignored. */ + ImportRef *importRefPtr; /* List of each imported Command created in + * another namespace when this command is + * imported. These imported commands redirect + * invocations back to this command. The list + * is used to remove all those imported + * commands when deleting this "real" + * command. */ + CommandTrace *tracePtr; /* First in list of all traces set for this + * command. */ +} Command; + +/*ARGSUSED*/ +Tcl_Namespace * +Blt_GetCommandNamespace(Tcl_Command cmdToken) +{ + Command *cmdPtr = (Command *)cmdToken; + + return (Tcl_Namespace *)cmdPtr->nsPtr; +} + +Tcl_CallFrame * +Blt_EnterNamespace(Tcl_Interp *interp, Tcl_Namespace *nsPtr) +{ + Tcl_CallFrame *framePtr; + + framePtr = Blt_AssertMalloc(sizeof(Tcl_CallFrame)); + if (Tcl_PushCallFrame(interp, framePtr, (Tcl_Namespace *)nsPtr, 0) + != TCL_OK) { + Blt_Free(framePtr); + return NULL; + } + return framePtr; +} + +void +Blt_LeaveNamespace(Tcl_Interp *interp, Tcl_CallFrame *framePtr) +{ + Tcl_PopCallFrame(interp); + Blt_Free(framePtr); +} + +int +Blt_ParseObjectName(Tcl_Interp *interp, const char *path, + Blt_ObjectName *namePtr, unsigned int flags) +{ + char *last, *colon; + + namePtr->nsPtr = NULL; + namePtr->name = NULL; + colon = NULL; + + /* Find the last namespace separator in the qualified name. */ + last = (char *)(path + strlen(path)); + while (--last > path) { + if ((*last == ':') && (*(last - 1) == ':')) { + last++; /* just after the last "::" */ + colon = last - 2; + break; + } + } + if (colon == NULL) { + namePtr->name = path; + if ((flags & BLT_NO_DEFAULT_NS) == 0) { + namePtr->nsPtr = Tcl_GetCurrentNamespace(interp); + } + return TRUE; /* No namespace designated in name. */ + } + + /* Separate the namespace and the object name. */ + *colon = '\0'; + if (path[0] == '\0') { + namePtr->nsPtr = Tcl_GetGlobalNamespace(interp); + } else { + namePtr->nsPtr = Tcl_FindNamespace(interp, (char *)path, NULL, + (flags & BLT_NO_ERROR_MSG) ? 0 : TCL_LEAVE_ERR_MSG); + } + /* Repair the string. */ *colon = ':'; + + if (namePtr->nsPtr == NULL) { + return FALSE; /* Namespace doesn't exist. */ + } + namePtr->name =last; + return TRUE; +} + +char * +Blt_MakeQualifiedName(Blt_ObjectName *namePtr, Tcl_DString *resultPtr) +{ + Tcl_DStringInit(resultPtr); + if ((namePtr->nsPtr->fullName[0] != ':') || + (namePtr->nsPtr->fullName[1] != ':') || + (namePtr->nsPtr->fullName[2] != '\0')) { + Tcl_DStringAppend(resultPtr, namePtr->nsPtr->fullName, -1); + } + Tcl_DStringAppend(resultPtr, "::", -1); + Tcl_DStringAppend(resultPtr, (char *)namePtr->name, -1); + return Tcl_DStringValue(resultPtr); +} + diff --git a/src/bltNsUtil.h b/src/bltNsUtil.h new file mode 100644 index 0000000..00769f3 --- /dev/null +++ b/src/bltNsUtil.h @@ -0,0 +1,101 @@ +/* + * bltNsUtil.h -- + * + * Copyright 1993-2004 George A Howlett. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY + * KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS + * OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef BLT_NS_UTIL_H +#define BLT_NS_UTIL_H 1 + +#ifndef TCL_NAMESPACE_ONLY +#define TCL_NAMESPACE_ONLY TCL_GLOBAL_ONLY +#endif + +#define NS_SEARCH_NONE (0) +#define NS_SEARCH_CURRENT (1<<0) +#define NS_SEARCH_GLOBAL (1<<1) +#define NS_SEARCH_BOTH (NS_SEARCH_GLOBAL | NS_SEARCH_CURRENT) + +typedef struct { + const char *name; + Tcl_Namespace *nsPtr; +} Blt_ObjectName; + +#define BLT_NO_DEFAULT_NS (1<<0) +#define BLT_NO_ERROR_MSG (1<<1) + +#ifndef USE_TCL_STUBS + +BLT_EXTERN Tcl_Command Tcl_FindCommand(Tcl_Interp *interp, const char *name, + Tcl_Namespace *nsPtr, int flags); +/* + * Namespace procedures not prototyped defined in Tcl.h + */ +BLT_EXTERN Tcl_Namespace *Tcl_GetCurrentNamespace(Tcl_Interp *interp); + +BLT_EXTERN Tcl_Namespace *Tcl_GetGlobalNamespace(Tcl_Interp *interp); + +BLT_EXTERN Tcl_Namespace *Tcl_CreateNamespace(Tcl_Interp *interp, + const char *name, ClientData clientData, + Tcl_NamespaceDeleteProc *nsDelProc); + +BLT_EXTERN void Tcl_DeleteNamespace(Tcl_Namespace *nsPtr); + +BLT_EXTERN Tcl_Namespace *Tcl_FindNamespace(Tcl_Interp *interp, + const char *name, Tcl_Namespace *context, int flags); + +BLT_EXTERN int Tcl_Export(Tcl_Interp *interp, Tcl_Namespace *nsPtr, + const char *name, int resetFlag); + +BLT_EXTERN Tcl_Var Tcl_FindNamespaceVar(Tcl_Interp *interp, const char *name, + Tcl_Namespace *contextNsPtr, int flags); + +BLT_EXTERN void Tcl_PopCallFrame(Tcl_Interp *interp); + +BLT_EXTERN int Tcl_PushCallFrame(Tcl_Interp *interp, Tcl_CallFrame *framePtr, + Tcl_Namespace *nsPtr, int isProcCallFrame); + +#endif /* USE_TCL_STUBS */ + +/* + * Auxillary procedures + */ +BLT_EXTERN Tcl_Namespace *Blt_GetVariableNamespace(Tcl_Interp *interp, + const char *varName); + +BLT_EXTERN Tcl_Namespace *Blt_GetCommandNamespace(Tcl_Command cmdToken); + +BLT_EXTERN Tcl_CallFrame *Blt_EnterNamespace(Tcl_Interp *interp, + Tcl_Namespace *nsPtr); + +BLT_EXTERN void Blt_LeaveNamespace(Tcl_Interp *interp, Tcl_CallFrame *framePtr); + +BLT_EXTERN int Blt_ParseObjectName(Tcl_Interp *interp, const char *name, + Blt_ObjectName *objNamePtr, unsigned int flags); + +BLT_EXTERN char *Blt_MakeQualifiedName(Blt_ObjectName *objNamePtr, + Tcl_DString *resultPtr); + +#endif /* BLT_NS_UTIL_H */ diff --git a/src/bltOp.h b/src/bltOp.h new file mode 100644 index 0000000..5887a46 --- /dev/null +++ b/src/bltOp.h @@ -0,0 +1,37 @@ + +/* + *--------------------------------------------------------------------------- + * + * Blt_OpSpec -- + * + * Structure to specify a set of operations for a TCL command. + * This is passed to the Blt_GetOp procedure to look + * for a function pointer associated with the operation name. + * + *--------------------------------------------------------------------------- + */ +typedef struct { + const char *name; /* Name of operation */ + int minChars; /* Minimum # characters to disambiguate */ + void *proc; + int minArgs; /* Minimum # args required */ + int maxArgs; /* Maximum # args required */ + const char *usage; /* Usage message */ +} Blt_OpSpec; + +typedef enum { + BLT_OP_ARG0, /* Op is the first argument. */ + BLT_OP_ARG1, /* Op is the second argument. */ + BLT_OP_ARG2, /* Op is the third argument. */ + BLT_OP_ARG3, /* Op is the fourth argument. */ + BLT_OP_ARG4 /* Op is the fifth argument. */ + +} Blt_OpIndex; + +#define BLT_OP_BINARY_SEARCH 0 +#define BLT_OP_LINEAR_SEARCH 1 + +BLT_EXTERN void *Blt_GetOpFromObj(Tcl_Interp *interp, int nSpecs, + Blt_OpSpec *specs, int operPos, int objc, Tcl_Obj *const *objv, + int flags); + diff --git a/src/bltParse.C b/src/bltParse.C new file mode 100644 index 0000000..10703f3 --- /dev/null +++ b/src/bltParse.C @@ -0,0 +1,546 @@ + +/* + * bltParse.c -- + * + * Contains a collection of procedures that are used to parse Tcl + * commands or parts of commands (like quoted strings or nested + * sub-commands). + * + * This file is copied from tclParse.c in the TCL library distribution. + * + * Copyright (c) 1987-1993 The Regents of the University of + * California. + * + * Copyright (c) 1994-1998 Sun Microsystems, Inc. + * + */ + +/* + * Since TCL 8.1.0 these routines have been replaced by ones that + * generate byte-codes. But since these routines are used in vector + * expressions, where no such byte-compilation is necessary, I now + * include them. In fact, the byte-compiled versions would be slower + * since the compiled code typically runs only one time. + */ +#include <bltInt.h> + +#include "bltParse.h" + +/* + * A table used to classify input characters to assist in parsing + * TCL commands. The table should be indexed with a signed character + * using the CHAR_TYPE macro. The character may have a negative + * value. The CHAR_TYPE macro takes a pointer to a signed character + * and a pointer to the last character in the source string. If the + * src pointer is pointing at the terminating null of the string, + * CHAR_TYPE returns TCL_COMMAND_END. + */ + +#define STATIC_STRING_SPACE 150 +#define UCHAR(c) ((unsigned char) (c)) +#define TCL_NORMAL 0x01 +#define TCL_SPACE 0x02 +#define TCL_COMMAND_END 0x04 +#define TCL_QUOTE 0x08 +#define TCL_OPEN_BRACKET 0x10 +#define TCL_OPEN_BRACE 0x20 +#define TCL_CLOSE_BRACE 0x40 +#define TCL_BACKSLASH 0x80 +#define TCL_DOLLAR 0x00 + +/* + * The following table assigns a type to each character. Only types + * meaningful to TCL parsing are represented here. The table is + * designed to be referenced with either signed or unsigned characters, + * so it has 384 entries. The first 128 entries correspond to negative + * character values, the next 256 correspond to positive character + * values. The last 128 entries are identical to the first 128. The + * table is always indexed with a 128-byte offset (the 128th entry + * corresponds to a 0 character value). + */ + +static unsigned char tclTypeTable[] = +{ + /* + * Negative character values, from -128 to -1: + */ + + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + + /* + * Positive character values, from 0-127: + */ + + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_SPACE, TCL_COMMAND_END, TCL_SPACE, + TCL_SPACE, TCL_SPACE, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_SPACE, TCL_NORMAL, TCL_QUOTE, TCL_NORMAL, + TCL_DOLLAR, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_COMMAND_END, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_OPEN_BRACKET, + TCL_BACKSLASH, TCL_COMMAND_END, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_OPEN_BRACE, + TCL_NORMAL, TCL_CLOSE_BRACE, TCL_NORMAL, TCL_NORMAL, + + /* + * Large unsigned character values, from 128-255: + */ + + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, +}; + +#define CHAR_TYPE(src,last) \ + (((src)==(last))?TCL_COMMAND_END:(tclTypeTable+128)[(int)*(src)]) + +/* + *--------------------------------------------------------------------------- + * + * Blt_ParseNestedCmd -- + * + * This procedure parses a nested TCL command between + * brackets, returning the result of the command. + * + * Results: + * The return value is a standard TCL result, which is + * TCL_OK unless there was an error while executing the + * nested command. If an error occurs then interp->result + * contains a standard error message. *TermPtr is filled + * in with the address of the character just after the + * last one processed; this is usually the character just + * after the matching close-bracket, or the null character + * at the end of the string if the close-bracket was missing + * (a missing close bracket is an error). The result returned + * by the command is stored in standard fashion in *parsePtr, + * null-terminated, with parsePtr->next pointing to the null + * character. + * + * Side effects: + * The storage space at *parsePtr may be expanded. + * + *--------------------------------------------------------------------------- + */ +int +Blt_ParseNestedCmd( + Tcl_Interp *interp, /* Interpreter to use for nested command + * evaluations and error messages. */ + const char *string, /* Character just after opening bracket. */ + int flags, /* Flags to pass to nested Tcl_Eval. */ + const char **termPtr, /* Store address of terminating character + * here. */ + ParseValue *parsePtr) /* Information about where to place + * result of command. */ +{ + int result, length, shortfall; + Interp *iPtr = (Interp *)interp; + + iPtr->evalFlags = flags | TCL_BRACKET_TERM; + result = Tcl_Eval(interp, string); + *termPtr = (string + iPtr->termOffset); + if (result != TCL_OK) { + /* + * The increment below results in slightly cleaner message in + * the errorInfo variable (the close-bracket will appear). + */ + + if (**termPtr == ']') { + *termPtr += 1; + } + return result; + } + (*termPtr) += 1; + length = (int)strlen(iPtr->result); + shortfall = length + 1 - (parsePtr->end - parsePtr->next); + if (shortfall > 0) { + (*parsePtr->expandProc) (parsePtr, shortfall); + } + strcpy(parsePtr->next, iPtr->result); + parsePtr->next += length; + + Tcl_FreeResult(interp); + iPtr->result = iPtr->resultSpace; + iPtr->resultSpace[0] = '\0'; + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_ParseBraces -- + * + * This procedure scans the information between matching + * curly braces. + * + * Results: + * The return value is a standard TCL result, which is + * TCL_OK unless there was an error while parsing string. + * If an error occurs then interp->result contains a + * standard error message. *TermPtr is filled + * in with the address of the character just after the + * last one successfully processed; this is usually the + * character just after the matching close-brace. The + * information between curly braces is stored in standard + * fashion in *parsePtr, null-terminated with parsePtr->next + * pointing to the terminating null character. + * + * Side effects: + * The storage space at *parsePtr may be expanded. + * + *--------------------------------------------------------------------------- + */ + +int +Blt_ParseBraces( + Tcl_Interp *interp, /* Interpreter to use for nested command + * evaluations and error messages. */ + const char *string, /* Character just after opening bracket. */ + const char **termPtr, /* Store address of terminating character + * here. */ + ParseValue *parsePtr) /* Information about where to place + * result of command. */ +{ + int level; + const char *src; + char *dest, *end; + char c; + const char *lastChar = string + strlen(string); + + src = string; + dest = parsePtr->next; + end = parsePtr->end; + level = 1; + + /* + * Copy the characters one at a time to the result area, stopping + * when the matching close-brace is found. + */ + + for (;;) { + c = *src; + src++; + + if (dest == end) { + parsePtr->next = dest; + (*parsePtr->expandProc) (parsePtr, 20); + dest = parsePtr->next; + end = parsePtr->end; + } + *dest = c; + dest++; + + if (CHAR_TYPE(src - 1, lastChar) == TCL_NORMAL) { + continue; + } else if (c == '{') { + level++; + } else if (c == '}') { + level--; + if (level == 0) { + dest--; /* Don't copy the last close brace. */ + break; + } + } else if (c == '\\') { + int count; + + /* + * Must always squish out backslash-newlines, even when in + * braces. This is needed so that this sequence can appear + * anywhere in a command, such as the middle of an expression. + */ + + if (*src == '\n') { + dest[-1] = Tcl_Backslash(src - 1, &count); + src += count - 1; + } else { + Tcl_Backslash(src - 1, &count); + while (count > 1) { + if (dest == end) { + parsePtr->next = dest; + (*parsePtr->expandProc) (parsePtr, 20); + dest = parsePtr->next; + end = parsePtr->end; + } + *dest = *src; + dest++; + src++; + count--; + } + } + } else if (c == '\0') { + Tcl_AppendResult(interp, "missing close-brace", (char *)NULL); + *termPtr = string - 1; + return TCL_ERROR; + } + } + + *dest = '\0'; + parsePtr->next = dest; + *termPtr = src; + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_ExpandParseValue -- + * + * This procedure is commonly used as the value of the + * expandProc in a ParseValue. It uses malloc to allocate + * more space for the result of a parse. + * + * Results: + * The buffer space in *parsePtr is reallocated to something + * larger, and if parsePtr->clientData is non-zero the old + * buffer is freed. Information is copied from the old + * buffer to the new one. + * + * Side effects: + * None. + * + *--------------------------------------------------------------------------- + */ +void +Blt_ExpandParseValue( + ParseValue *parsePtr, /* Information about buffer that + * must be expanded. If the clientData + * in the structure is non-zero, it + * means that the current buffer is + * dynamically allocated. */ + int needed) /* Minimum amount of additional space + * to allocate. */ +{ + int size; + char *buffer; + + /* + * Either double the size of the buffer or add enough new space + * to meet the demand, whichever produces a larger new buffer. + */ + size = (parsePtr->end - parsePtr->buffer) + 1; + if (size < needed) { + size += needed; + } else { + size += size; + } + buffer = Blt_AssertMalloc((unsigned int)size); + + /* + * Copy from old buffer to new, free old buffer if needed, and + * mark new buffer as malloc-ed. + */ + memcpy((VOID *) buffer, (VOID *) parsePtr->buffer, + (size_t) (parsePtr->next - parsePtr->buffer)); + parsePtr->next = buffer + (parsePtr->next - parsePtr->buffer); + if (parsePtr->clientData != 0) { + Blt_Free(parsePtr->buffer); + } + parsePtr->buffer = buffer; + parsePtr->end = buffer + size - 1; + parsePtr->clientData = (ClientData)1; +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_ParseQuotes -- + * + * This procedure parses a double-quoted string such as a + * quoted TCL command argument or a quoted value in a Tcl + * expression. This procedure is also used to parse array + * element names within parentheses, or anything else that + * needs all the substitutions that happen in quotes. + * + * Results: + * The return value is a standard TCL result, which is + * TCL_OK unless there was an error while parsing the + * quoted string. If an error occurs then interp->result + * contains a standard error message. *TermPtr is filled + * in with the address of the character just after the + * last one successfully processed; this is usually the + * character just after the matching close-quote. The + * fully-substituted contents of the quotes are stored in + * standard fashion in *parsePtr, null-terminated with + * parsePtr->next pointing to the terminating null character. + * + * Side effects: + * The buffer space in parsePtr may be enlarged by calling its + * expandProc. + * + *--------------------------------------------------------------------------- + */ +int +Blt_ParseQuotes( + Tcl_Interp *interp, /* Interpreter to use for nested command + * evaluations and error messages. */ + const char *string, /* Character just after opening double- + * quote. */ + int termChar, /* Character that terminates "quoted" string + * (usually double-quote, but sometimes + * right-paren or something else). */ + int flags, /* Flags to pass to nested Tcl_Eval calls. */ + const char **termPtr, /* Store address of terminating character + * here. */ + ParseValue *parsePtr) /* Information about where to place + * fully-substituted result of parse. */ +{ + const char *src; + char *dest, c; + const char *lastChar = string + strlen(string); + + src = string; + dest = parsePtr->next; + + for (;;) { + if (dest == parsePtr->end) { + /* + * Target buffer space is about to run out. Make more space. + */ + parsePtr->next = dest; + (*parsePtr->expandProc) (parsePtr, 1); + dest = parsePtr->next; + } + c = *src; + src++; + if (c == termChar) { + *dest = '\0'; + parsePtr->next = dest; + *termPtr = src; + return TCL_OK; + } else if (CHAR_TYPE(src - 1, lastChar) == TCL_NORMAL) { + copy: + *dest = c; + dest++; + continue; + } else if (c == '$') { + int length; + const char *value; + + value = Tcl_ParseVar(interp, src - 1, termPtr); + if (value == NULL) { + return TCL_ERROR; + } + src = *termPtr; + length = strlen(value); + if ((parsePtr->end - dest) <= length) { + parsePtr->next = dest; + (*parsePtr->expandProc) (parsePtr, length); + dest = parsePtr->next; + } + strcpy(dest, value); + dest += length; + continue; + } else if (c == '[') { + int result; + + parsePtr->next = dest; + result = Blt_ParseNestedCmd(interp, src, flags, termPtr, parsePtr); + if (result != TCL_OK) { + return result; + } + src = *termPtr; + dest = parsePtr->next; + continue; + } else if (c == '\\') { + int nRead; + + src--; + *dest = Tcl_Backslash(src, &nRead); + dest++; + src += nRead; + continue; + } else if (c == '\0') { + char buf[10]; + + Tcl_ResetResult(interp); + sprintf_s(buf, 10, "missing %c", termChar); + Tcl_SetStringObj(Tcl_GetObjResult(interp), buf, 9); + *termPtr = string - 1; + return TCL_ERROR; + } else { + goto copy; + } + } +} + diff --git a/src/bltParse.h b/src/bltParse.h new file mode 100644 index 0000000..3ff26cb --- /dev/null +++ b/src/bltParse.h @@ -0,0 +1,43 @@ + +/* + * bltParse.h -- + * + * Copyright 1993-2004 George A Howlett. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef _BLT_PARSE_H +#define _BLT_PARSE_H + +#include "tclInterp.h" + +BLT_EXTERN int Blt_ParseBraces(Tcl_Interp *interp, const char *string, + const char **termPtr, ParseValue *pvPtr); + +BLT_EXTERN int Blt_ParseNestedCmd(Tcl_Interp *interp, const char *string, + int flags, const char **termPtr, ParseValue *pvPtr); + +BLT_EXTERN int Blt_ParseQuotes(Tcl_Interp *interp, const char *string, + int termChar, int flags, const char **termPtr, ParseValue * pvPtr); + +BLT_EXTERN void Blt_ExpandParseValue(ParseValue *pvPtr, int needed); + +#endif diff --git a/src/bltSwitch.C b/src/bltSwitch.C new file mode 100644 index 0000000..35228d1 --- /dev/null +++ b/src/bltSwitch.C @@ -0,0 +1,554 @@ + +/* + * bltSwitch.c -- + * + * This module implements command/argument switch parsing procedures for the + * BLT toolkit. + * + * Copyright 1991-2004 George A Howlett. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include "bltInt.h" +#include <stdarg.h> + +#include "bltSwitch.h" + +static void +DoHelp(Tcl_Interp *interp, Blt_SwitchSpec *specs) +{ + Tcl_DString ds; + Blt_SwitchSpec *sp; + + Tcl_DStringInit(&ds); + Tcl_DStringAppend(&ds, "following switches are available:", -1); + for (sp = specs; sp->type != BLT_SWITCH_END; sp++) { + Tcl_DStringAppend(&ds, "\n ", 4); + Tcl_DStringAppend(&ds, sp->switchName, -1); + Tcl_DStringAppend(&ds, " ", 1); + Tcl_DStringAppend(&ds, sp->help, -1); + } + Tcl_AppendResult(interp, Tcl_DStringValue(&ds), (char *)NULL); + Tcl_DStringFree(&ds); +} + +/* + *--------------------------------------------------------------------------- + * + * FindSwitchSpec -- + * + * Search through a table of configuration specs, looking for one that + * matches a given argvName. + * + * Results: + * The return value is a pointer to the matching entry, or NULL if + * nothing matched. In that case an error message is left in the + * interp's result. + * + * Side effects: + * None. + * + *--------------------------------------------------------------------------- + */ +static Blt_SwitchSpec * +FindSwitchSpec( + Tcl_Interp *interp, /* Used for reporting errors. */ + Blt_SwitchSpec *specs, /* Pointer to table of configuration + * specifications for a widget. */ + const char *name, /* Name identifying a particular switch. */ + int length, /* Length of name. */ + int needFlags, /* Flags that must be present in matching + * entry. */ + int hateFlags) /* Flags that must NOT be present in matching + * entry. */ +{ + Blt_SwitchSpec *sp; + char c; /* First character of current argument. */ + Blt_SwitchSpec *matchPtr; /* Matching spec, or NULL. */ + + c = name[1]; + matchPtr = NULL; + for (sp = specs; sp->type != BLT_SWITCH_END; sp++) { + if (sp->switchName == NULL) { + continue; + } + if (((sp->flags & needFlags) != needFlags) || (sp->flags & hateFlags)) { + continue; + } + if ((sp->switchName[1] != c) || + (strncmp(sp->switchName, name, length) != 0)) { + continue; + } + if (sp->switchName[length] == '\0') { + return sp; /* Stop on a perfect match. */ + } + if (matchPtr != NULL) { + Tcl_AppendResult(interp, "ambiguous switch \"", name, "\"\n", + (char *) NULL); + DoHelp(interp, specs); + return NULL; + } + matchPtr = sp; + } + if (strcmp(name, "-help") == 0) { + DoHelp(interp, specs); + return NULL; + } + if (matchPtr == NULL) { + Tcl_AppendResult(interp, "unknown switch \"", name, "\"\n", + (char *)NULL); + DoHelp(interp, specs); + return NULL; + } + return matchPtr; +} + +/* + *--------------------------------------------------------------------------- + * + * DoSwitch -- + * + * This procedure applies a single configuration switch to a widget + * record. + * + * Results: + * A standard TCL return value. + * + * Side effects: + * WidgRec is modified as indicated by specPtr and value. The old value + * is recycled, if that is appropriate for the value type. + * + *--------------------------------------------------------------------------- + */ +static int +DoSwitch( + Tcl_Interp *interp, /* Interpreter for error reporting. */ + Blt_SwitchSpec *sp, /* Specifier to apply. */ + Tcl_Obj *objPtr, /* Value to use to fill in widgRec. */ + void *record) /* Record whose fields are to be modified. + * Values must be properly initialized. */ +{ + do { + char *ptr; + + ptr = (char *)record + sp->offset; + switch (sp->type) { + case BLT_SWITCH_BOOLEAN: + { + int bool; + + if (Tcl_GetBooleanFromObj(interp, objPtr, &bool) != TCL_OK) { + return TCL_ERROR; + } + if (sp->mask > 0) { + if (bool) { + *((int *)ptr) |= sp->mask; + } else { + *((int *)ptr) &= ~sp->mask; + } + } else { + *((int *)ptr) = bool; + } + } + break; + + case BLT_SWITCH_DOUBLE: + if (Tcl_GetDoubleFromObj(interp, objPtr, (double *)ptr) != TCL_OK) { + return TCL_ERROR; + } + break; + + case BLT_SWITCH_OBJ: + Tcl_IncrRefCount(objPtr); + *(Tcl_Obj **)ptr = objPtr; + break; + + case BLT_SWITCH_FLOAT: + { + double value; + + if (Tcl_GetDoubleFromObj(interp, objPtr, &value) != TCL_OK) { + return TCL_ERROR; + } + *(float *)ptr = (float)value; + } + break; + + case BLT_SWITCH_INT: + if (Tcl_GetIntFromObj(interp, objPtr, (int *)ptr) != TCL_OK) { + return TCL_ERROR; + } + break; + + case BLT_SWITCH_INT_NNEG: + { + long value; + + if (Blt_GetCountFromObj(interp, objPtr, COUNT_NNEG, + &value) != TCL_OK) { + return TCL_ERROR; + } + *(int *)ptr = (int)value; + } + break; + + case BLT_SWITCH_INT_POS: + { + long value; + + if (Blt_GetCountFromObj(interp, objPtr, COUNT_POS, + &value) != TCL_OK) { + return TCL_ERROR; + } + *(int *)ptr = (int)value; + } + break; + + case BLT_SWITCH_LIST: + { + int argc; + + if (Tcl_SplitList(interp, Tcl_GetString(objPtr), &argc, + (const char ***)ptr) != TCL_OK) { + return TCL_ERROR; + } + } + break; + + case BLT_SWITCH_LONG: + if (Tcl_GetLongFromObj(interp, objPtr, (long *)ptr) != TCL_OK) { + return TCL_ERROR; + } + break; + + case BLT_SWITCH_LONG_NNEG: + { + long value; + + if (Blt_GetCountFromObj(interp, objPtr, COUNT_NNEG, + &value) != TCL_OK) { + return TCL_ERROR; + } + *(long *)ptr = value; + } + break; + + case BLT_SWITCH_LONG_POS: + { + long value; + + if (Blt_GetCountFromObj(interp, objPtr, COUNT_POS, &value) + != TCL_OK) { + return TCL_ERROR; + } + *(long *)ptr = value; + } + break; + + case BLT_SWITCH_STRING: + { + char *value; + + value = Tcl_GetString(objPtr); + value = (*value == '\0') ? NULL : Blt_AssertStrdup(value); + if (*(char **)ptr != NULL) { + Blt_Free(*(char **)ptr); + } + *(char **)ptr = value; + } +#ifdef notdef + { + char *old, *new, **strPtr; + char *string; + + string = Tcl_GetString(objPtr); + strPtr = (char **)ptr; + new = ((*string == '\0') && (sp->flags & BLT_SWITCH_NULL_OK)) + ? NULL : Blt_AssertStrdup(string); + old = *strPtr; + if (old != NULL) { + Blt_Free(old); + } + *strPtr = new; + } +#endif + break; + + case BLT_SWITCH_CUSTOM: + assert(sp->customPtr != NULL); + if ((*sp->customPtr->parseProc)(sp->customPtr->clientData, interp, + sp->switchName, objPtr, (char *)record, sp->offset, sp->flags) + != TCL_OK) { + return TCL_ERROR; + } + break; + + default: + Tcl_AppendResult(interp, "bad switch table: unknown type \"", + Blt_Itoa(sp->type), "\"", (char *)NULL); + return TCL_ERROR; + } + sp++; + } while ((sp->switchName == NULL) && (sp->type != BLT_SWITCH_END)); + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_ParseSwitches -- + * + * Process command-line switches to fill in fields of a record with + * resources and other parameters. + * + * Results: + * Returns the number of arguments comsumed by parsing the command line. + * If an error occurred, -1 will be returned and an error messages can be + * found as the interpreter result. + * + * Side effects: + * The fields of widgRec get filled in with information from argc/argv. + * Old information in widgRec's fields gets recycled. + * + *--------------------------------------------------------------------------- + */ +int +Blt_ParseSwitches( + Tcl_Interp *interp, /* Interpreter for error reporting. */ + Blt_SwitchSpec *specs, /* Describes legal switches. */ + int objc, /* Number of elements in argv. */ + Tcl_Obj *const *objv, /* Command-line switches. */ + void *record, /* Record whose fields are to be modified. + * Values must be properly initialized. */ + int flags) /* Used to specify additional flags that must + * be present in switch specs for them to be + * considered. */ +{ + Blt_SwitchSpec *sp; + int count; + int needFlags; /* Specs must contain this set of flags or + * else they are not considered. */ + int hateFlags; /* If a spec contains any bits here, it's not + * considered. */ + + needFlags = flags & ~(BLT_SWITCH_USER_BIT - 1); + hateFlags = 0; + + /* + * Pass 1: Clear the change flags on all the specs so that we + * can check it later. + */ + for (sp = specs; sp->type != BLT_SWITCH_END; sp++) { + sp->flags &= ~BLT_SWITCH_SPECIFIED; + } + /* + * Pass 2: Process the arguments that match entries in the specs. + * It's an error if the argument doesn't match anything. + */ + for (count = 0; count < objc; count++) { + char *arg; + int length; + + arg = Tcl_GetStringFromObj(objv[count], &length); + if (flags & BLT_SWITCH_OBJV_PARTIAL) { + /* + * If the argument doesn't start with a '-' (not a switch) or is + * '--', stop processing and return the number of arguments + * comsumed. + */ + if (arg[0] != '-') { + return count; + } + if ((arg[1] == '-') && (arg[2] == '\0')) { + return count + 1; /* include the "--" in the count. */ + } + } + sp = FindSwitchSpec(interp, specs, arg, length, needFlags, hateFlags); + if (sp == NULL) { + return -1; + } + if (sp->type == BLT_SWITCH_BITMASK) { + char *ptr; + + ptr = (char *)record + sp->offset; + *((int *)ptr) |= sp->mask; + } else if (sp->type == BLT_SWITCH_BITMASK_INVERT) { + char *ptr; + + ptr = (char *)record + sp->offset; + *((int *)ptr) &= ~sp->mask; + } else if (sp->type == BLT_SWITCH_VALUE) { + char *ptr; + + ptr = (char *)record + sp->offset; + *((int *)ptr) = sp->mask; + } else { + count++; + if (count == objc) { + Tcl_AppendResult(interp, "value for \"", arg, "\" missing", + (char *) NULL); + return -1; + } + if (DoSwitch(interp, sp, objv[count], record) != TCL_OK) { + char msg[200]; + + sprintf_s(msg, 200, "\n (processing \"%.40s\" switch)", + sp->switchName); + Tcl_AddErrorInfo(interp, msg); + return -1; + } + } + sp->flags |= BLT_SWITCH_SPECIFIED; + } + return count; +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_FreeSwitches -- + * + * Free up all resources associated with switches. + * + * Results: + * None. + * + *--------------------------------------------------------------------------- + */ +/* ARGSUSED */ +void +Blt_FreeSwitches( + Blt_SwitchSpec *specs, /* Describes legal switches. */ + void *record, /* Record whose fields contain current values + * for switches. */ + int needFlags) /* Used to specify additional flags that must + * be present in config specs for them to be + * considered. */ +{ + Blt_SwitchSpec *sp; + + for (sp = specs; sp->type != BLT_SWITCH_END; sp++) { + if ((sp->flags & needFlags) == needFlags) { + char *ptr; + + ptr = (char *)record + sp->offset; + switch (sp->type) { + case BLT_SWITCH_STRING: + case BLT_SWITCH_LIST: + if (*((char **) ptr) != NULL) { + Blt_Free(*((char **) ptr)); + *((char **) ptr) = NULL; + } + break; + + case BLT_SWITCH_OBJ: + if (*((Tcl_Obj **) ptr) != NULL) { + Tcl_DecrRefCount(*((Tcl_Obj **)ptr)); + *((Tcl_Obj **) ptr) = NULL; + } + break; + + case BLT_SWITCH_CUSTOM: + assert(sp->customPtr != NULL); + if ((*(char **)ptr != NULL) && + (sp->customPtr->freeProc != NULL)) { + (*sp->customPtr->freeProc)((char *)record, sp->offset, + sp->flags); + } + break; + + default: + break; + } + } + } +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_SwitchModified -- + * + * Given the configuration specifications and one or more switch patterns + * (terminated by a NULL), indicate if any of the matching switches has + * been reset. + * + * Results: + * Returns 1 if one of the switches have changed, 0 otherwise. + * + *--------------------------------------------------------------------------- + */ +int +Blt_SwitchChanged TCL_VARARGS_DEF(Blt_SwitchSpec *, arg1) +{ + va_list argList; + Blt_SwitchSpec *specs; + Blt_SwitchSpec *sp; + char *switchName; + + specs = TCL_VARARGS_START(Blt_SwitchSpec *, arg1, argList); + while ((switchName = va_arg(argList, char *)) != NULL) { + for (sp = specs; sp->type != BLT_SWITCH_END; sp++) { + if ((Tcl_StringMatch(sp->switchName, switchName)) && + (sp->flags & BLT_SWITCH_SPECIFIED)) { + va_end(argList); + return 1; + } + } + } + va_end(argList); + return 0; +} + +int +Blt_ExprDoubleFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, double *valuePtr) +{ + /* First try to extract the value as a double precision number. */ + if (Tcl_GetDoubleFromObj((Tcl_Interp *)NULL, objPtr, valuePtr) == TCL_OK) { + return TCL_OK; + } + /* Then try to parse it as an expression. */ + if (Tcl_ExprDouble(interp, Tcl_GetString(objPtr), valuePtr) == TCL_OK) { + return TCL_OK; + } + return TCL_ERROR; +} + +int +Blt_ExprIntFromObj( + Tcl_Interp *interp, + Tcl_Obj *objPtr, + int *valuePtr) +{ + long lvalue; + + /* First try to extract the value as a simple integer. */ + if (Tcl_GetIntFromObj((Tcl_Interp *)NULL, objPtr, valuePtr) == TCL_OK) { + return TCL_OK; + } + /* Otherwise try to parse it as an expression. */ + if (Tcl_ExprLong(interp, Tcl_GetString(objPtr), &lvalue) == TCL_OK) { + *valuePtr = lvalue; + return TCL_OK; + } + return TCL_ERROR; +} + diff --git a/src/bltSwitch.h b/src/bltSwitch.h new file mode 100644 index 0000000..603c73f --- /dev/null +++ b/src/bltSwitch.h @@ -0,0 +1,136 @@ + +/* + * bltSwitch.h -- + * + * Copyright 1993-2004 George A Howlett. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY + * KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS + * OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef BLT_SWITCH_H +#define BLT_SWITCH_H + +#ifdef HAVE_STDDEF_H +# include <stddef.h> +#endif /* HAVE_STDDEF_H */ + +#ifndef Blt_Offset +#ifdef offsetof +#define Blt_Offset(type, field) ((int) offsetof(type, field)) +#else +#define Blt_Offset(type, field) ((int) ((char *) &((type *) 0)->field)) +#endif +#endif /* Blt_Offset */ + +typedef int (Blt_SwitchParseProc)(ClientData clientData, Tcl_Interp *interp, + const char *switchName, Tcl_Obj *valueObjPtr, char *record, int offset, + int flags); +typedef void (Blt_SwitchFreeProc)(char *record, int offset, int flags); + +typedef struct { + Blt_SwitchParseProc *parseProc; /* Procedure to parse a switch + * value and store it in its * + * converted form in the data * + * record. */ + + Blt_SwitchFreeProc *freeProc; /* Procedure to free a switch. */ + + ClientData clientData; /* Arbitrary one-word value used by + * switch parser, passed to + * parseProc. */ +} Blt_SwitchCustom; + + +/* + * Type values for Blt_SwitchSpec structures. See the user + * documentation for details. + */ +typedef enum { + BLT_SWITCH_BOOLEAN, + BLT_SWITCH_DOUBLE, + BLT_SWITCH_BITMASK, + BLT_SWITCH_BITMASK_INVERT, + BLT_SWITCH_FLOAT, + BLT_SWITCH_INT, + BLT_SWITCH_INT_NNEG, + BLT_SWITCH_INT_POS, + BLT_SWITCH_LIST, + BLT_SWITCH_LONG, + BLT_SWITCH_LONG_NNEG, + BLT_SWITCH_LONG_POS, + BLT_SWITCH_OBJ, + BLT_SWITCH_STRING, + BLT_SWITCH_VALUE, + BLT_SWITCH_CUSTOM, + BLT_SWITCH_END +} Blt_SwitchTypes; + + +typedef struct { + Blt_SwitchTypes type; /* Type of option, such as + * BLT_SWITCH_COLOR; see definitions + * below. Last option in table must + * have type BLT_SWITCH_END. */ + + const char *switchName; /* Switch used to specify option in + * argv. NULL means this spec is part + * of a group. */ + + const char *help; /* Help string. */ + int offset; /* Where in widget record to store + * value; use Blt_Offset macro to + * generate values for this. */ + + int flags; /* Any combination of the values + * defined below. */ + + unsigned int mask; + + Blt_SwitchCustom *customPtr; /* If type is BLT_SWITCH_CUSTOM then + * this is a pointer to info about how + * to parse and print the option. + * Otherwise it is irrelevant. */ +} Blt_SwitchSpec; + +#define BLT_SWITCH_DEFAULTS (0) +#define BLT_SWITCH_ARGV_PARTIAL (1<<1) +#define BLT_SWITCH_OBJV_PARTIAL (1<<1) + +/* + * Possible flag values for Blt_SwitchSpec structures. Any bits at or + * above BLT_SWITCH_USER_BIT may be used by clients for selecting + * certain entries. + */ +#define BLT_SWITCH_NULL_OK (1<<0) +#define BLT_SWITCH_DONT_SET_DEFAULT (1<<3) +#define BLT_SWITCH_SPECIFIED (1<<4) +#define BLT_SWITCH_USER_BIT (1<<8) + +BLT_EXTERN int Blt_ParseSwitches(Tcl_Interp *interp, Blt_SwitchSpec *specPtr, + int objc, Tcl_Obj *const *objv, void *rec, int flags); + +BLT_EXTERN void Blt_FreeSwitches(Blt_SwitchSpec *specs, void *rec, int flags); + +BLT_EXTERN int Blt_SwitchChanged TCL_VARARGS(Blt_SwitchSpec *, specs); + +#endif /* BLT_SWITCH_H */ diff --git a/src/bltVecCmd.C b/src/bltVecCmd.C new file mode 100644 index 0000000..c113347 --- /dev/null +++ b/src/bltVecCmd.C @@ -0,0 +1,2414 @@ + +/* + * bltVecCmd.c -- + * + * This module implements vector data objects. + * + * Copyright 1995-2004 George A Howlett. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * Code for binary data read operation was donated by Harold Kirsch. + * + */ + +/* + * TODO: + * o Add H. Kirsch's vector binary read operation + * x binread file0 + * x binread -file file0 + * + * o Add ASCII/binary file reader + * x read fileName + * + * o Allow Tcl-based client notifications. + * vector x + * x notify call Display + * x notify delete Display + * x notify reorder #1 #2 + */ + +#include "bltVecInt.h" +#include "bltOp.h" +#include "bltNsUtil.h" +#include "bltSwitch.h" + +typedef int (VectorCmdProc)(Vector *vPtr, Tcl_Interp *interp, int objc, + Tcl_Obj *const *objv); + +static Blt_SwitchParseProc ObjToFFTVector; +static Blt_SwitchCustom fftVectorSwitch = { + ObjToFFTVector, NULL, (ClientData)0, +}; + +static Blt_SwitchParseProc ObjToIndex; +static Blt_SwitchCustom indexSwitch = { + ObjToIndex, NULL, (ClientData)0, +}; + +typedef struct { + Tcl_Obj *formatObjPtr; + int from, to; +} PrintSwitches; + +static Blt_SwitchSpec printSwitches[] = +{ + {BLT_SWITCH_OBJ, "-format", "string", + Blt_Offset(PrintSwitches, formatObjPtr), 0}, + {BLT_SWITCH_CUSTOM, "-from", "index", + Blt_Offset(PrintSwitches, from), 0, 0, &indexSwitch}, + {BLT_SWITCH_CUSTOM, "-to", "index", + Blt_Offset(PrintSwitches, to), 0, 0, &indexSwitch}, + {BLT_SWITCH_END} +}; + + +typedef struct { + int flags; +} SortSwitches; + +#define SORT_DECREASING (1<<0) +#define SORT_UNIQUE (1<<1) + +static Blt_SwitchSpec sortSwitches[] = +{ + {BLT_SWITCH_BITMASK, "-decreasing", "", + Blt_Offset(SortSwitches, flags), 0, SORT_DECREASING}, + {BLT_SWITCH_BITMASK, "-reverse", "", + Blt_Offset(SortSwitches, flags), 0, SORT_DECREASING}, + {BLT_SWITCH_BITMASK, "-uniq", "", + Blt_Offset(SortSwitches, flags), 0, SORT_UNIQUE}, + {BLT_SWITCH_END} +}; + +typedef struct { + double delta; + Vector *imagPtr; /* Vector containing imaginary part. */ + Vector *freqPtr; /* Vector containing frequencies. */ + VectorInterpData *dataPtr; + int mask; /* Flags controlling FFT. */ +} FFTData; + + +static Blt_SwitchSpec fftSwitches[] = { + {BLT_SWITCH_CUSTOM, "-imagpart", "vector", + Blt_Offset(FFTData, imagPtr), 0, 0, &fftVectorSwitch}, + {BLT_SWITCH_BITMASK, "-noconstant", "", + Blt_Offset(FFTData, mask), 0, FFT_NO_CONSTANT}, + {BLT_SWITCH_BITMASK, "-spectrum", "", + Blt_Offset(FFTData, mask), 0, FFT_SPECTRUM}, + {BLT_SWITCH_BITMASK, "-bartlett", "", + Blt_Offset(FFTData, mask), 0, FFT_BARTLETT}, + {BLT_SWITCH_DOUBLE, "-delta", "float", + Blt_Offset(FFTData, mask), 0, 0, }, + {BLT_SWITCH_CUSTOM, "-frequencies", "vector", + Blt_Offset(FFTData, freqPtr), 0, 0, &fftVectorSwitch}, + {BLT_SWITCH_END} +}; + +/* + *--------------------------------------------------------------------------- + * + * ObjToFFTVector -- + * + * Convert a string representing a vector into its vector structure. + * + * Results: + * The return value is a standard TCL result. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +ObjToFFTVector( + ClientData clientData, /* Not used. */ + Tcl_Interp *interp, /* Interpreter to send results back to */ + const char *switchName, /* Not used. */ + Tcl_Obj *objPtr, /* Name of vector. */ + char *record, /* Structure record */ + int offset, /* Offset to field in structure */ + int flags) /* Not used. */ +{ + FFTData *dataPtr = (FFTData *)record; + Vector *vPtr; + Vector **vPtrPtr = (Vector **)(record + offset); + int isNew; /* Not used. */ + char *string; + + string = Tcl_GetString(objPtr); + vPtr = Blt_Vec_Create(dataPtr->dataPtr, string, string, string, &isNew); + if (vPtr == NULL) { + return TCL_ERROR; + } + *vPtrPtr = vPtr; + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * ObjToIndex -- + * + * Convert a string representing a vector into its vector structure. + * + * Results: + * The return value is a standard TCL result. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +ObjToIndex( + ClientData clientData, /* Contains the vector in question to verify + * its length. */ + Tcl_Interp *interp, /* Interpreter to send results back to */ + const char *switchName, /* Not used. */ + Tcl_Obj *objPtr, /* Name of vector. */ + char *record, /* Structure record */ + int offset, /* Offset to field in structure */ + int flags) /* Not used. */ +{ + Vector *vPtr = clientData; + int *indexPtr = (int *)(record + offset); + int index; + + if (Blt_Vec_GetIndex(interp, vPtr, Tcl_GetString(objPtr), &index, + INDEX_CHECK, (Blt_VectorIndexProc **)NULL) != TCL_OK) { + return TCL_ERROR; + } + *indexPtr = index; + return TCL_OK; + +} + +static Tcl_Obj * +GetValues(Vector *vPtr, int first, int last) +{ + Tcl_Obj *listObjPtr; + double *vp, *vend; + + listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL); + for (vp = vPtr->valueArr + first, vend = vPtr->valueArr + last; vp <= vend; + vp++) { + Tcl_ListObjAppendElement(vPtr->interp, listObjPtr, + Tcl_NewDoubleObj(*vp)); + } + return listObjPtr; +} + +static void +ReplicateValue(Vector *vPtr, int first, int last, double value) +{ + double *vp, *vend; + + for (vp = vPtr->valueArr + first, vend = vPtr->valueArr + last; + vp <= vend; vp++) { + *vp = value; + } + vPtr->notifyFlags |= UPDATE_RANGE; +} + +static int +CopyList(Vector *vPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) +{ + int i; + + if (Blt_Vec_SetLength(interp, vPtr, objc) != TCL_OK) { + return TCL_ERROR; + } + for (i = 0; i < objc; i++) { + double value; + + if (Blt_ExprDoubleFromObj(interp, objv[i], &value) != TCL_OK) { + Blt_Vec_SetLength(interp, vPtr, i); + return TCL_ERROR; + } + vPtr->valueArr[i] = value; + } + return TCL_OK; +} + +static int +AppendVector(Vector *destPtr, Vector *srcPtr) +{ + size_t nBytes; + size_t oldSize, newSize; + + oldSize = destPtr->length; + newSize = oldSize + srcPtr->last - srcPtr->first + 1; + if (Blt_Vec_ChangeLength(destPtr->interp, destPtr, newSize) != TCL_OK) { + return TCL_ERROR; + } + nBytes = (newSize - oldSize) * sizeof(double); + memcpy((char *)(destPtr->valueArr + oldSize), + (srcPtr->valueArr + srcPtr->first), nBytes); + destPtr->notifyFlags |= UPDATE_RANGE; + return TCL_OK; +} + +static int +AppendList(Vector *vPtr, int objc, Tcl_Obj *const *objv) +{ + Tcl_Interp *interp = vPtr->interp; + int count; + int i; + double value; + int oldSize; + + oldSize = vPtr->length; + if (Blt_Vec_ChangeLength(interp, vPtr, vPtr->length + objc) != TCL_OK) { + return TCL_ERROR; + } + count = oldSize; + for (i = 0; i < objc; i++) { + if (Blt_ExprDoubleFromObj(interp, objv[i], &value) != TCL_OK) { + Blt_Vec_ChangeLength(interp, vPtr, count); + return TCL_ERROR; + } + vPtr->valueArr[count++] = value; + } + vPtr->notifyFlags |= UPDATE_RANGE; + return TCL_OK; +} + +/* Vector instance option commands */ + +/* + *--------------------------------------------------------------------------- + * + * AppendOp -- + * + * Appends one of more TCL lists of values, or vector objects onto the + * end of the current vector object. + * + * Results: + * A standard TCL result. If a current vector can't be created, + * resized, any of the named vectors can't be found, or one of lists of + * values is invalid, TCL_ERROR is returned. + * + * Side Effects: + * Clients of current vector will be notified of the change. + * + *--------------------------------------------------------------------------- + */ +static int +AppendOp(Vector *vPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) +{ + int i; + int result; + Vector *v2Ptr; + + for (i = 2; i < objc; i++) { + v2Ptr = Blt_Vec_ParseElement((Tcl_Interp *)NULL, vPtr->dataPtr, + Tcl_GetString(objv[i]), (const char **)NULL, NS_SEARCH_BOTH); + if (v2Ptr != NULL) { + result = AppendVector(vPtr, v2Ptr); + } else { + int nElem; + Tcl_Obj **elemObjArr; + + if (Tcl_ListObjGetElements(interp, objv[i], &nElem, &elemObjArr) + != TCL_OK) { + return TCL_ERROR; + } + result = AppendList(vPtr, nElem, elemObjArr); + } + if (result != TCL_OK) { + return TCL_ERROR; + } + } + if (objc > 2) { + if (vPtr->flush) { + Blt_Vec_FlushCache(vPtr); + } + Blt_Vec_UpdateClients(vPtr); + } + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * ClearOp -- + * + * Deletes all the accumulated array indices for the TCL array associated + * will the vector. This routine can be used to free excess memory from + * a large vector. + * + * Results: + * Always returns TCL_OK. + * + * Side Effects: + * Memory used for the entries of the TCL array variable is freed. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +ClearOp(Vector *vPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) +{ + Blt_Vec_FlushCache(vPtr); + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * DeleteOp -- + * + * Deletes the given indices from the vector. If no indices are provided + * the entire vector is deleted. + * + * Results: + * A standard TCL result. If any of the given indices is invalid, + * interp->result will an error message and TCL_ERROR is returned. + * + * Side Effects: + * The clients of the vector will be notified of the vector + * deletions. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +DeleteOp(Vector *vPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) +{ + unsigned char *unsetArr; + int i, j; + int count; + char *string; + + /* FIXME: Don't delete vector with no indices. */ + if (objc == 2) { + Blt_Vec_Free(vPtr); + return TCL_OK; + } + + /* Allocate an "unset" bitmap the size of the vector. */ + unsetArr = Blt_AssertCalloc(sizeof(unsigned char), (vPtr->length + 7) / 8); +#define SetBit(i) \ + unsetArr[(i) >> 3] |= (1 << ((i) & 0x07)) +#define GetBit(i) \ + (unsetArr[(i) >> 3] & (1 << ((i) & 0x07))) + + for (i = 2; i < objc; i++) { + string = Tcl_GetString(objv[i]); + if (Blt_Vec_GetIndexRange(interp, vPtr, string, + (INDEX_COLON | INDEX_CHECK), (Blt_VectorIndexProc **) NULL) + != TCL_OK) { + Blt_Free(unsetArr); + return TCL_ERROR; + } + for (j = vPtr->first; j <= vPtr->last; j++) { + SetBit(j); /* Mark the range of elements for deletion. */ + } + } + count = 0; + for (i = 0; i < vPtr->length; i++) { + if (GetBit(i)) { + continue; /* Skip elements marked for deletion. */ + } + if (count < i) { + vPtr->valueArr[count] = vPtr->valueArr[i]; + } + count++; + } + Blt_Free(unsetArr); + vPtr->length = count; + if (vPtr->flush) { + Blt_Vec_FlushCache(vPtr); + } + Blt_Vec_UpdateClients(vPtr); + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * DupOp -- + * + * Creates one or more duplicates of the vector object. + * + * Results: + * A standard TCL result. If a new vector can't be created, + * or and existing vector resized, TCL_ERROR is returned. + * + * Side Effects: + * Clients of existing vectors will be notified of the change. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +DupOp(Vector *vPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) +{ + int i; + + for (i = 2; i < objc; i++) { + Vector *v2Ptr; + char *name; + int isNew; + + name = Tcl_GetString(objv[i]); + v2Ptr = Blt_Vec_Create(vPtr->dataPtr, name, name, name, &isNew); + if (v2Ptr == NULL) { + return TCL_ERROR; + } + if (v2Ptr == vPtr) { + continue; + } + if (Blt_Vec_Duplicate(v2Ptr, vPtr) != TCL_OK) { + return TCL_ERROR; + } + if (!isNew) { + if (v2Ptr->flush) { + Blt_Vec_FlushCache(v2Ptr); + } + Blt_Vec_UpdateClients(v2Ptr); + } + } + return TCL_OK; +} + + +/* spinellia@acm.org START */ + +/* fft implementation */ +/*ARGSUSED*/ +static int +FFTOp(Vector *vPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) +{ + Vector *v2Ptr = NULL; + int isNew; + FFTData data; + char *realVecName; + + memset(&data, 0, sizeof(data)); + data.delta = 1.0; + + realVecName = Tcl_GetString(objv[2]); + v2Ptr = Blt_Vec_Create(vPtr->dataPtr, realVecName, realVecName, + realVecName, &isNew); + if (v2Ptr == NULL) { + return TCL_ERROR; + } + if (v2Ptr == vPtr) { + Tcl_AppendResult(interp, "real vector \"", realVecName, "\"", + " can't be the same as the source", (char *)NULL); + return TCL_ERROR; + } + if (Blt_ParseSwitches(interp, fftSwitches, objc - 3, objv + 3, &data, + BLT_SWITCH_DEFAULTS) < 0) { + return TCL_ERROR; + } + if (Blt_Vec_FFT(interp, v2Ptr, data.imagPtr, data.freqPtr, data.delta, + data.mask, vPtr) != TCL_OK) { + return TCL_ERROR; + } + /* Update bookkeeping. */ + if (!isNew) { + if (v2Ptr->flush) { + Blt_Vec_FlushCache(v2Ptr); + } + Blt_Vec_UpdateClients(v2Ptr); + } + if (data.imagPtr != NULL) { + if (data.imagPtr->flush) { + Blt_Vec_FlushCache(data.imagPtr); + } + Blt_Vec_UpdateClients(data.imagPtr); + } + if (data.freqPtr != NULL) { + if (data.freqPtr->flush) { + Blt_Vec_FlushCache(data.freqPtr); + } + Blt_Vec_UpdateClients(data.freqPtr); + } + return TCL_OK; +} + +/*ARGSUSED*/ +static int +InverseFFTOp(Vector *vPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) +{ + int isNew; + char *name; + Vector *srcImagPtr; + Vector *destRealPtr; + Vector *destImagPtr; + + name = Tcl_GetString(objv[2]); + if (Blt_Vec_LookupName(vPtr->dataPtr, name, &srcImagPtr) != TCL_OK ) { + return TCL_ERROR; + } + name = Tcl_GetString(objv[3]); + destRealPtr = Blt_Vec_Create(vPtr->dataPtr, name, name, name, &isNew); + name = Tcl_GetString(objv[4]); + destImagPtr = Blt_Vec_Create(vPtr->dataPtr, name, name, name, &isNew); + + if (Blt_Vec_InverseFFT(interp, srcImagPtr, destRealPtr, destImagPtr, vPtr) + != TCL_OK ){ + return TCL_ERROR; + } + if (destRealPtr->flush) { + Blt_Vec_FlushCache(destRealPtr); + } + Blt_Vec_UpdateClients(destRealPtr); + + if (destImagPtr->flush) { + Blt_Vec_FlushCache(destImagPtr); + } + Blt_Vec_UpdateClients(destImagPtr); + return TCL_OK; +} + + +/* + *--------------------------------------------------------------------------- + * + * IndexOp -- + * + * Sets or reads the value of the index. This simulates what the + * vector's variable does. + * + * Results: + * A standard TCL result. If the index is invalid, + * interp->result will an error message and TCL_ERROR is returned. + * Otherwise interp->result will contain the values. + * + *--------------------------------------------------------------------------- + */ +static int +IndexOp(Vector *vPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) +{ + int first, last; + char *string; + + string = Tcl_GetString(objv[2]); + if (Blt_Vec_GetIndexRange(interp, vPtr, string, INDEX_ALL_FLAGS, + (Blt_VectorIndexProc **) NULL) != TCL_OK) { + return TCL_ERROR; + } + first = vPtr->first, last = vPtr->last; + if (objc == 3) { + Tcl_Obj *listObjPtr; + + if (first == vPtr->length) { + Tcl_AppendResult(interp, "can't get index \"", string, "\"", + (char *)NULL); + return TCL_ERROR; /* Can't read from index "++end" */ + } + listObjPtr = GetValues(vPtr, first, last); + Tcl_SetObjResult(interp, listObjPtr); + } else { + double value; + + /* FIXME: huh? Why set values here?. */ + if (first == SPECIAL_INDEX) { + Tcl_AppendResult(interp, "can't set index \"", string, "\"", + (char *)NULL); + return TCL_ERROR; /* Tried to set "min" or "max" */ + } + if (Blt_ExprDoubleFromObj(interp, objv[3], &value) != TCL_OK) { + return TCL_ERROR; + } + if (first == vPtr->length) { + if (Blt_Vec_ChangeLength(interp, vPtr, vPtr->length + 1) + != TCL_OK) { + return TCL_ERROR; + } + } + ReplicateValue(vPtr, first, last, value); + Tcl_SetObjResult(interp, objv[3]); + if (vPtr->flush) { + Blt_Vec_FlushCache(vPtr); + } + Blt_Vec_UpdateClients(vPtr); + } + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * LengthOp -- + * + * Returns the length of the vector. If a new size is given, the + * vector is resized to the new vector. + * + * Results: + * A standard TCL result. If the new length is invalid, + * interp->result will an error message and TCL_ERROR is returned. + * Otherwise interp->result will contain the length of the vector. + * + *--------------------------------------------------------------------------- + */ +static int +LengthOp(Vector *vPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) +{ + if (objc == 3) { + int nElem; + + if (Tcl_GetIntFromObj(interp, objv[2], &nElem) != TCL_OK) { + return TCL_ERROR; + } + if (nElem < 0) { + Tcl_AppendResult(interp, "bad vector size \"", + Tcl_GetString(objv[2]), "\"", (char *)NULL); + return TCL_ERROR; + } + if ((Blt_Vec_SetSize(interp, vPtr, nElem) != TCL_OK) || + (Blt_Vec_SetLength(interp, vPtr, nElem) != TCL_OK)) { + return TCL_ERROR; + } + if (vPtr->flush) { + Blt_Vec_FlushCache(vPtr); + } + Blt_Vec_UpdateClients(vPtr); + } + Tcl_SetIntObj(Tcl_GetObjResult(interp), vPtr->length); + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * MapOp -- + * + * Queries or sets the offset of the array index from the base + * address of the data array of values. + * + * Results: + * A standard TCL result. If the source vector doesn't exist + * or the source list is not a valid list of numbers, TCL_ERROR + * returned. Otherwise TCL_OK is returned. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +MapOp(Vector *vPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) +{ + if (objc > 2) { + if (Blt_Vec_MapVariable(interp, vPtr, Tcl_GetString(objv[2])) + != TCL_OK) { + return TCL_ERROR; + } + } + if (vPtr->arrayName != NULL) { + Tcl_SetStringObj(Tcl_GetObjResult(interp), vPtr->arrayName, -1); + } + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * MaxOp -- + * + * Returns the maximum value of the vector. + * + * Results: + * A standard TCL result. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +MaxOp(Vector *vPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) +{ + Tcl_SetDoubleObj(Tcl_GetObjResult(interp), Blt_Vec_Max(vPtr)); + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * MergeOp -- + * + * Merges the values from the given vectors to the current vector. + * + * Results: + * A standard TCL result. If any of the given vectors differ in size, + * TCL_ERROR is returned. Otherwise TCL_OK is returned and the + * vector data will contain merged values of the given vectors. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +MergeOp(Vector *vPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) +{ + Vector **vecArr; + int refSize, nElem; + int i; + double *valuePtr, *valueArr; + Vector **vPtrPtr; + + /* Allocate an array of vector pointers of each vector to be + * merged in the current vector. */ + vecArr = Blt_AssertMalloc(sizeof(Vector *) * objc); + vPtrPtr = vecArr; + + refSize = -1; + nElem = 0; + for (i = 2; i < objc; i++) { + Vector *v2Ptr; + int length; + + if (Blt_Vec_LookupName(vPtr->dataPtr, Tcl_GetString(objv[i]), &v2Ptr) + != TCL_OK) { + Blt_Free(vecArr); + return TCL_ERROR; + } + /* Check that all the vectors are the same length */ + length = v2Ptr->last - v2Ptr->first + 1; + if (refSize < 0) { + refSize = length; + } else if (length != refSize) { + Tcl_AppendResult(vPtr->interp, "vectors \"", vPtr->name, + "\" and \"", v2Ptr->name, "\" differ in length", + (char *)NULL); + Blt_Free(vecArr); + return TCL_ERROR; + } + *vPtrPtr++ = v2Ptr; + nElem += refSize; + } + *vPtrPtr = NULL; + + valueArr = Blt_Malloc(sizeof(double) * nElem); + if (valueArr == NULL) { + Tcl_AppendResult(vPtr->interp, "not enough memory to allocate ", + Blt_Itoa(nElem), " vector elements", (char *)NULL); + return TCL_ERROR; + } + + /* Merge the values from each of the vectors into the current vector */ + valuePtr = valueArr; + for (i = 0; i < refSize; i++) { + Vector **vpp; + + for (vpp = vecArr; *vpp != NULL; vpp++) { + *valuePtr++ = (*vpp)->valueArr[i + (*vpp)->first]; + } + } + Blt_Free(vecArr); + Blt_Vec_Reset(vPtr, valueArr, nElem, nElem, TCL_DYNAMIC); + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * MinOp -- + * + * Returns the minimum value of the vector. + * + * Results: + * A standard TCL result. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +MinOp(Vector *vPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) +{ + Tcl_SetDoubleObj(Tcl_GetObjResult(interp), Blt_Vec_Min(vPtr)); + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * NormalizeOp -- + * + * Normalizes the vector. + * + * Results: + * A standard TCL result. If the density is invalid, TCL_ERROR + * is returned. Otherwise TCL_OK is returned. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +NormalizeOp(Vector *vPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) +{ + int i; + double range; + + Blt_Vec_UpdateRange(vPtr); + range = vPtr->max - vPtr->min; + if (objc > 2) { + Vector *v2Ptr; + int isNew; + char *string; + + string = Tcl_GetString(objv[2]); + v2Ptr = Blt_Vec_Create(vPtr->dataPtr, string, string, string, &isNew); + if (v2Ptr == NULL) { + return TCL_ERROR; + } + if (Blt_Vec_SetLength(interp, v2Ptr, vPtr->length) != TCL_OK) { + return TCL_ERROR; + } + for (i = 0; i < vPtr->length; i++) { + v2Ptr->valueArr[i] = (vPtr->valueArr[i] - vPtr->min) / range; + } + Blt_Vec_UpdateRange(v2Ptr); + if (!isNew) { + if (v2Ptr->flush) { + Blt_Vec_FlushCache(v2Ptr); + } + Blt_Vec_UpdateClients(v2Ptr); + } + } else { + Tcl_Obj *listObjPtr; + + listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL); + for (i = 0; i < vPtr->length; i++) { + double norm; + + norm = (vPtr->valueArr[i] - vPtr->min) / range; + Tcl_ListObjAppendElement(interp, listObjPtr, + Tcl_NewDoubleObj(norm)); + } + Tcl_SetObjResult(interp, listObjPtr); + } + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * NotifyOp -- + * + * Notify clients of vector. + * + * Results: + * A standard TCL result. If any of the given vectors differ in size, + * TCL_ERROR is returned. Otherwise TCL_OK is returned and the + * vector data will contain merged values of the given vectors. + * + * x vector notify now + * x vector notify always + * x vector notify whenidle + * x vector notify update {} + * x vector notify delete {} + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +NotifyOp(Vector *vPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) +{ + int option; + int bool; + enum optionIndices { + OPTION_ALWAYS, OPTION_NEVER, OPTION_WHENIDLE, + OPTION_NOW, OPTION_CANCEL, OPTION_PENDING + }; + static const char *optionArr[] = { + "always", "never", "whenidle", "now", "cancel", "pending", NULL + }; + + if (Tcl_GetIndexFromObj(interp, objv[2], optionArr, "qualifier", TCL_EXACT, + &option) != TCL_OK) { + return TCL_OK; + } + switch (option) { + case OPTION_ALWAYS: + vPtr->notifyFlags &= ~NOTIFY_WHEN_MASK; + vPtr->notifyFlags |= NOTIFY_ALWAYS; + break; + case OPTION_NEVER: + vPtr->notifyFlags &= ~NOTIFY_WHEN_MASK; + vPtr->notifyFlags |= NOTIFY_NEVER; + break; + case OPTION_WHENIDLE: + vPtr->notifyFlags &= ~NOTIFY_WHEN_MASK; + vPtr->notifyFlags |= NOTIFY_WHENIDLE; + break; + case OPTION_NOW: + /* FIXME: How does this play when an update is pending? */ + Blt_Vec_NotifyClients(vPtr); + break; + case OPTION_CANCEL: + if (vPtr->notifyFlags & NOTIFY_PENDING) { + vPtr->notifyFlags &= ~NOTIFY_PENDING; + Tcl_CancelIdleCall(Blt_Vec_NotifyClients, (ClientData)vPtr); + } + break; + case OPTION_PENDING: + bool = (vPtr->notifyFlags & NOTIFY_PENDING); + Tcl_SetBooleanObj(Tcl_GetObjResult(interp), bool); + break; + } + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * PopulateOp -- + * + * Creates or resizes a new vector based upon the density specified. + * + * Results: + * A standard TCL result. If the density is invalid, TCL_ERROR + * is returned. Otherwise TCL_OK is returned. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +PopulateOp(Vector *vPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) +{ + Vector *v2Ptr; + int size, density; + int isNew; + int i, j; + double *valuePtr; + int count; + char *string; + + string = Tcl_GetString(objv[2]); + v2Ptr = Blt_Vec_Create(vPtr->dataPtr, string, string, string, &isNew); + if (v2Ptr == NULL) { + return TCL_ERROR; + } + if (vPtr->length == 0) { + return TCL_OK; /* Source vector is empty. */ + } + if (Tcl_GetIntFromObj(interp, objv[3], &density) != TCL_OK) { + return TCL_ERROR; + } + if (density < 1) { + Tcl_AppendResult(interp, "bad density \"", Tcl_GetString(objv[3]), + "\"", (char *)NULL); + return TCL_ERROR; + } + size = (vPtr->length - 1) * (density + 1) + 1; + if (Blt_Vec_SetLength(interp, v2Ptr, size) != TCL_OK) { + return TCL_ERROR; + } + count = 0; + valuePtr = v2Ptr->valueArr; + for (i = 0; i < (vPtr->length - 1); i++) { + double slice, range; + + range = vPtr->valueArr[i + 1] - vPtr->valueArr[i]; + slice = range / (double)(density + 1); + for (j = 0; j <= density; j++) { + *valuePtr = vPtr->valueArr[i] + (slice * (double)j); + valuePtr++; + count++; + } + } + count++; + *valuePtr = vPtr->valueArr[i]; + assert(count == v2Ptr->length); + if (!isNew) { + if (v2Ptr->flush) { + Blt_Vec_FlushCache(v2Ptr); + } + Blt_Vec_UpdateClients(v2Ptr); + } + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * ValuesOp -- + * + * Print the values vector. + * + * Results: + * A standard TCL result. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +ValuesOp(Vector *vPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) +{ + PrintSwitches switches; + + switches.formatObjPtr = NULL; + switches.from = 0; + switches.to = vPtr->length - 1; + indexSwitch.clientData = vPtr; + if (Blt_ParseSwitches(interp, printSwitches, objc - 2, objv + 2, &switches, + BLT_SWITCH_DEFAULTS) < 0) { + return TCL_ERROR; + } + if (switches.from > switches.to) { + int tmp; + /* swap positions. */ + tmp = switches.to; + switches.to = switches.from; + switches.from = tmp; + } + if (switches.formatObjPtr == NULL) { + Tcl_Obj *listObjPtr; + int i; + + listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL); + for (i = switches.from; i <= switches.to; i++) { + Tcl_ListObjAppendElement(interp, listObjPtr, + Tcl_NewDoubleObj(vPtr->valueArr[i])); + } + Tcl_SetObjResult(interp, listObjPtr); + } else { + Tcl_DString ds; + char buffer[200]; + const char *fmt; + int i; + + Tcl_DStringInit(&ds); + fmt = Tcl_GetString(switches.formatObjPtr); + for (i = switches.from; i <= switches.to; i++) { + sprintf(buffer, fmt, vPtr->valueArr[i]); + Tcl_DStringAppend(&ds, buffer, -1); + } + Tcl_DStringResult(interp, &ds); + Tcl_DStringFree(&ds); + } + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * RangeOp -- + * + * Returns a TCL list of the range of vector values specified. + * + * Results: + * A standard TCL result. If the given range is invalid, TCL_ERROR + * is returned. Otherwise TCL_OK is returned and interp->result + * will contain the list of values. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +RangeOp(Vector *vPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) +{ + Tcl_Obj *listObjPtr; + int first, last; + int i; + + if (objc == 2) { + first = 0; + last = vPtr->length - 1; + } else if (objc == 4) { + if ((Blt_Vec_GetIndex(interp, vPtr, Tcl_GetString(objv[2]), &first, + INDEX_CHECK, (Blt_VectorIndexProc **) NULL) != TCL_OK) || + (Blt_Vec_GetIndex(interp, vPtr, Tcl_GetString(objv[3]), &last, + INDEX_CHECK, (Blt_VectorIndexProc **) NULL) != TCL_OK)) { + return TCL_ERROR; + } + } else { + Tcl_AppendResult(interp, "wrong # args: should be \"", + Tcl_GetString(objv[0]), " range ?first last?", (char *)NULL); + return TCL_ERROR; + } + listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL); + if (first > last) { + /* Return the list reversed */ + for (i = last; i <= first; i++) { + Tcl_ListObjAppendElement(interp, listObjPtr, + Tcl_NewDoubleObj(vPtr->valueArr[i])); + } + } else { + for (i = first; i <= last; i++) { + Tcl_ListObjAppendElement(interp, listObjPtr, + Tcl_NewDoubleObj(vPtr->valueArr[i])); + } + } + Tcl_SetObjResult(interp, listObjPtr); + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * InRange -- + * + * Determines if a value lies within a given range. + * + * The value is normalized and compared against the interval + * [0..1], where 0.0 is the minimum and 1.0 is the maximum. + * DBL_EPSILON is the smallest number that can be represented + * on the host machine, such that (1.0 + epsilon) != 1.0. + * + * Please note, min cannot be greater than max. + * + * Results: + * If the value is within of the interval [min..max], 1 is + * returned; 0 otherwise. + * + *--------------------------------------------------------------------------- + */ +INLINE static int +InRange(double value, double min, double max) +{ + double range; + + range = max - min; + if (range < DBL_EPSILON) { + return (FABS(max - value) < DBL_EPSILON); + } else { + double norm; + + norm = (value - min) / range; + return ((norm >= -DBL_EPSILON) && ((norm - 1.0) < DBL_EPSILON)); + } +} + +enum NativeFormats { + FMT_UNKNOWN = -1, + FMT_UCHAR, FMT_CHAR, + FMT_USHORT, FMT_SHORT, + FMT_UINT, FMT_INT, + FMT_ULONG, FMT_LONG, + FMT_FLOAT, FMT_DOUBLE +}; + +/* + *--------------------------------------------------------------------------- + * + * GetBinaryFormat + * + * Translates a format string into a native type. Valid formats are + * + * signed i1, i2, i4, i8 + * unsigned u1, u2, u4, u8 + * real r4, r8, r16 + * + * There must be a corresponding native type. For example, this for + * reading 2-byte binary integers from an instrument and converting them + * to unsigned shorts or ints. + * + *--------------------------------------------------------------------------- + */ +static enum NativeFormats +GetBinaryFormat(Tcl_Interp *interp, char *string, int *sizePtr) +{ + char c; + + c = tolower(string[0]); + if (Tcl_GetInt(interp, string + 1, sizePtr) != TCL_OK) { + Tcl_AppendResult(interp, "unknown binary format \"", string, + "\": incorrect byte size", (char *)NULL); + return FMT_UNKNOWN; + } + switch (c) { + case 'r': + if (*sizePtr == sizeof(double)) { + return FMT_DOUBLE; + } else if (*sizePtr == sizeof(float)) { + return FMT_FLOAT; + } + break; + + case 'i': + if (*sizePtr == sizeof(char)) { + return FMT_CHAR; + } else if (*sizePtr == sizeof(int)) { + return FMT_INT; + } else if (*sizePtr == sizeof(long)) { + return FMT_LONG; + } else if (*sizePtr == sizeof(short)) { + return FMT_SHORT; + } + break; + + case 'u': + if (*sizePtr == sizeof(unsigned char)) { + return FMT_UCHAR; + } else if (*sizePtr == sizeof(unsigned int)) { + return FMT_UINT; + } else if (*sizePtr == sizeof(unsigned long)) { + return FMT_ULONG; + } else if (*sizePtr == sizeof(unsigned short)) { + return FMT_USHORT; + } + break; + + default: + Tcl_AppendResult(interp, "unknown binary format \"", string, + "\": should be either i#, r#, u# (where # is size in bytes)", + (char *)NULL); + return FMT_UNKNOWN; + } + Tcl_AppendResult(interp, "can't handle format \"", string, "\"", + (char *)NULL); + return FMT_UNKNOWN; +} + +static int +CopyValues(Vector *vPtr, char *byteArr, enum NativeFormats fmt, int size, + int length, int swap, int *indexPtr) +{ + int i, n; + int newSize; + + if ((swap) && (size > 1)) { + int nBytes = size * length; + unsigned char *p; + int left, right; + + for (i = 0; i < nBytes; i += size) { + p = (unsigned char *)(byteArr + i); + for (left = 0, right = size - 1; left < right; left++, right--) { + p[left] ^= p[right]; + p[right] ^= p[left]; + p[left] ^= p[right]; + } + + } + } + newSize = *indexPtr + length; + if (newSize > vPtr->length) { + if (Blt_Vec_ChangeLength(vPtr->interp, vPtr, newSize) != TCL_OK) { + return TCL_ERROR; + } + } +#define CopyArrayToVector(vPtr, arr) \ + for (i = 0, n = *indexPtr; i < length; i++, n++) { \ + (vPtr)->valueArr[n] = (double)(arr)[i]; \ + } + + switch (fmt) { + case FMT_CHAR: + CopyArrayToVector(vPtr, (char *)byteArr); + break; + + case FMT_UCHAR: + CopyArrayToVector(vPtr, (unsigned char *)byteArr); + break; + + case FMT_INT: + CopyArrayToVector(vPtr, (int *)byteArr); + break; + + case FMT_UINT: + CopyArrayToVector(vPtr, (unsigned int *)byteArr); + break; + + case FMT_LONG: + CopyArrayToVector(vPtr, (long *)byteArr); + break; + + case FMT_ULONG: + CopyArrayToVector(vPtr, (unsigned long *)byteArr); + break; + + case FMT_SHORT: + CopyArrayToVector(vPtr, (short int *)byteArr); + break; + + case FMT_USHORT: + CopyArrayToVector(vPtr, (unsigned short int *)byteArr); + break; + + case FMT_FLOAT: + CopyArrayToVector(vPtr, (float *)byteArr); + break; + + case FMT_DOUBLE: + CopyArrayToVector(vPtr, (double *)byteArr); + break; + + case FMT_UNKNOWN: + break; + } + *indexPtr += length; + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * BinreadOp -- + * + * Reads binary values from a TCL channel. Values are either appended to + * the end of the vector or placed at a given index (using the "-at" + * option), overwriting existing values. Data is read until EOF is found + * on the channel or a specified number of values are read. (note that + * this is not necessarily the same as the number of bytes). + * + * The following flags are supported: + * -swap Swap bytes + * -at index Start writing data at the index. + * -format fmt Specifies the format of the data. + * + * This binary reader was created and graciously donated by Harald Kirsch + * (kir@iitb.fhg.de). Anything that's wrong is due to my (gah) munging + * of the code. + * + * Results: + * Returns a standard TCL result. The interpreter result will contain the + * number of values (not the number of bytes) read. + * + * Caveats: + * Channel reads must end on an element boundary. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +BinreadOp(Vector *vPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) +{ + Tcl_Channel channel; + char *byteArr; + char *string; + enum NativeFormats fmt; + int arraySize, bytesRead; + int count, total; + int first; + int size, length, mode; + int swap; + int i; + + string = Tcl_GetString(objv[2]); + channel = Tcl_GetChannel(interp, string, &mode); + if (channel == NULL) { + return TCL_ERROR; + } + if ((mode & TCL_READABLE) == 0) { + Tcl_AppendResult(interp, "channel \"", string, + "\" wasn't opened for reading", (char *)NULL); + return TCL_ERROR; + } + first = vPtr->length; + fmt = FMT_DOUBLE; + size = sizeof(double); + swap = FALSE; + count = 0; + + if (objc > 3) { + string = Tcl_GetString(objv[3]); + if (string[0] != '-') { + long int value; + /* Get the number of values to read. */ + if (Tcl_GetLongFromObj(interp, objv[3], &value) != TCL_OK) { + return TCL_ERROR; + } + if (value < 0) { + Tcl_AppendResult(interp, "count can't be negative", + (char *)NULL); + return TCL_ERROR; + } + count = (size_t)value; + objc--, objv++; + } + } + /* Process any option-value pairs that remain. */ + for (i = 3; i < objc; i++) { + string = Tcl_GetString(objv[i]); + if (strcmp(string, "-swap") == 0) { + swap = TRUE; + } else if (strcmp(string, "-format") == 0) { + i++; + if (i >= objc) { + Tcl_AppendResult(interp, "missing arg after \"", string, + "\"", (char *)NULL); + return TCL_ERROR; + } + string = Tcl_GetString(objv[i]); + fmt = GetBinaryFormat(interp, string, &size); + if (fmt == FMT_UNKNOWN) { + return TCL_ERROR; + } + } else if (strcmp(string, "-at") == 0) { + i++; + if (i >= objc) { + Tcl_AppendResult(interp, "missing arg after \"", string, + "\"", (char *)NULL); + return TCL_ERROR; + } + string = Tcl_GetString(objv[i]); + if (Blt_Vec_GetIndex(interp, vPtr, string, &first, 0, + (Blt_VectorIndexProc **)NULL) != TCL_OK) { + return TCL_ERROR; + } + if (first > vPtr->length) { + Tcl_AppendResult(interp, "index \"", string, + "\" is out of range", (char *)NULL); + return TCL_ERROR; + } + } + } + +#define BUFFER_SIZE 1024 + if (count == 0) { + arraySize = BUFFER_SIZE * size; + } else { + arraySize = count * size; + } + + byteArr = Blt_AssertMalloc(arraySize); + /* FIXME: restore old channel translation later? */ + if (Tcl_SetChannelOption(interp, channel, "-translation", + "binary") != TCL_OK) { + return TCL_ERROR; + } + total = 0; + while (!Tcl_Eof(channel)) { + bytesRead = Tcl_Read(channel, byteArr, arraySize); + if (bytesRead < 0) { + Tcl_AppendResult(interp, "error reading channel: ", + Tcl_PosixError(interp), (char *)NULL); + return TCL_ERROR; + } + if ((bytesRead % size) != 0) { + Tcl_AppendResult(interp, "error reading channel: short read", + (char *)NULL); + return TCL_ERROR; + } + length = bytesRead / size; + if (CopyValues(vPtr, byteArr, fmt, size, length, swap, &first) + != TCL_OK) { + return TCL_ERROR; + } + total += length; + if (count > 0) { + break; + } + } + Blt_Free(byteArr); + + if (vPtr->flush) { + Blt_Vec_FlushCache(vPtr); + } + Blt_Vec_UpdateClients(vPtr); + + /* Set the result as the number of values read. */ + Tcl_SetIntObj(Tcl_GetObjResult(interp), total); + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * SearchOp -- + * + * Searches for a value in the vector. Returns the indices of all vector + * elements matching a particular value. + * + * Results: + * Always returns TCL_OK. interp->result will contain a list of the + * indices of array elements matching value. If no elements match, + * interp->result will contain the empty string. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +SearchOp(Vector *vPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) +{ + double min, max; + int i; + int wantValue; + char *string; + Tcl_Obj *listObjPtr; + + wantValue = FALSE; + string = Tcl_GetString(objv[2]); + if ((string[0] == '-') && (strcmp(string, "-value") == 0)) { + wantValue = TRUE; + objv++, objc--; + } + if (Blt_ExprDoubleFromObj(interp, objv[2], &min) != TCL_OK) { + return TCL_ERROR; + } + max = min; + if (objc > 4) { + Tcl_AppendResult(interp, "wrong # arguments: should be \"", + Tcl_GetString(objv[0]), " search ?-value? min ?max?", + (char *)NULL); + return TCL_ERROR; + } + if ((objc > 3) && + (Blt_ExprDoubleFromObj(interp, objv[3], &max) != TCL_OK)) { + return TCL_ERROR; + } + if ((min - max) >= DBL_EPSILON) { + return TCL_OK; /* Bogus range. Don't bother looking. */ + } + listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL); + if (wantValue) { + for (i = 0; i < vPtr->length; i++) { + if (InRange(vPtr->valueArr[i], min, max)) { + Tcl_ListObjAppendElement(interp, listObjPtr, + Tcl_NewDoubleObj(vPtr->valueArr[i])); + } + } + } else { + for (i = 0; i < vPtr->length; i++) { + if (InRange(vPtr->valueArr[i], min, max)) { + Tcl_ListObjAppendElement(interp, listObjPtr, + Tcl_NewIntObj(i + vPtr->offset)); + } + } + } + Tcl_SetObjResult(interp, listObjPtr); + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * OffsetOp -- + * + * Queries or sets the offset of the array index from the base address of + * the data array of values. + * + * Results: + * A standard TCL result. If the source vector doesn't exist or the + * source list is not a valid list of numbers, TCL_ERROR returned. + * Otherwise TCL_OK is returned. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +OffsetOp(Vector *vPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) +{ + if (objc == 3) { + int newOffset; + + if (Tcl_GetIntFromObj(interp, objv[2], &newOffset) != TCL_OK) { + return TCL_ERROR; + } + vPtr->offset = newOffset; + } + Tcl_SetIntObj(Tcl_GetObjResult(interp), vPtr->offset); + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * RandomOp -- + * + * Generates random values for the length of the vector. + * + * Results: + * A standard TCL result. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +RandomOp(Vector *vPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) +{ + int i; + + for (i = 0; i < vPtr->length; i++) { + vPtr->valueArr[i] = drand48(); + } + if (vPtr->flush) { + Blt_Vec_FlushCache(vPtr); + } + Blt_Vec_UpdateClients(vPtr); + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * SeqOp -- + * + * Generates a sequence of values in the vector. + * + * Results: + * A standard TCL result. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +SeqOp(Vector *vPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) +{ + int n; + double start, stop; + + if (Blt_ExprDoubleFromObj(interp, objv[2], &start) != TCL_OK) { + return TCL_ERROR; + } + if (Blt_ExprDoubleFromObj(interp, objv[3], &stop) != TCL_OK) { + return TCL_ERROR; + } + n = vPtr->length; + if ((objc > 4) && (Blt_ExprIntFromObj(interp, objv[4], &n) != TCL_OK)) { + return TCL_ERROR; + } + if (n > 1) { + int i; + double step; + + if (Blt_Vec_SetLength(interp, vPtr, n) != TCL_OK) { + return TCL_ERROR; + } + step = (stop - start) / (double)(n - 1); + for (i = 0; i < n; i++) { + vPtr->valueArr[i] = start + (step * i); + } + if (vPtr->flush) { + Blt_Vec_FlushCache(vPtr); + } + Blt_Vec_UpdateClients(vPtr); + } + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * SetOp -- + * + * Sets the data of the vector object from a list of values. + * + * Results: + * A standard TCL result. If the source vector doesn't exist or the + * source list is not a valid list of numbers, TCL_ERROR returned. + * Otherwise TCL_OK is returned. + * + * Side Effects: + * The vector data is reset. Clients of the vector are notified. Any + * cached array indices are flushed. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +SetOp(Vector *vPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) +{ + int result; + Vector *v2Ptr; + int nElem; + Tcl_Obj **elemObjArr; + + /* The source can be either a list of numbers or another vector. */ + + v2Ptr = Blt_Vec_ParseElement((Tcl_Interp *)NULL, vPtr->dataPtr, + Tcl_GetString(objv[2]), NULL, NS_SEARCH_BOTH); + if (v2Ptr != NULL) { + if (vPtr == v2Ptr) { + Vector *tmpPtr; + /* + * Source and destination vectors are the same. Copy the source + * first into a temporary vector to avoid memory overlaps. + */ + tmpPtr = Blt_Vec_New(vPtr->dataPtr); + result = Blt_Vec_Duplicate(tmpPtr, v2Ptr); + if (result == TCL_OK) { + result = Blt_Vec_Duplicate(vPtr, tmpPtr); + } + Blt_Vec_Free(tmpPtr); + } else { + result = Blt_Vec_Duplicate(vPtr, v2Ptr); + } + } else if (Tcl_ListObjGetElements(interp, objv[2], &nElem, &elemObjArr) + == TCL_OK) { + result = CopyList(vPtr, interp, nElem, elemObjArr); + } else { + return TCL_ERROR; + } + + if (result == TCL_OK) { + /* + * The vector has changed; so flush the array indices (they're wrong + * now), find the new range of the data, and notify the vector's + * clients that it's been modified. + */ + if (vPtr->flush) { + Blt_Vec_FlushCache(vPtr); + } + Blt_Vec_UpdateClients(vPtr); + } + return result; +} + +/* + *--------------------------------------------------------------------------- + * + * SimplifyOp -- + * + * Sets the data of the vector object from a list of values. + * + * Results: + * A standard TCL result. If the source vector doesn't exist or the + * source list is not a valid list of numbers, TCL_ERROR returned. + * Otherwise TCL_OK is returned. + * + * Side Effects: + * The vector data is reset. Clients of the vector are notified. Any + * cached array indices are flushed. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +SimplifyOp(Vector *vPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) +{ + size_t i, n; + int length, nPoints; + int *simple; + double tolerance = 10.0; + Point2d *orig, *reduced; + + length = vPtr->length; + nPoints = vPtr->length / 2; + simple = Blt_AssertMalloc(nPoints * sizeof(int)); + reduced = Blt_AssertMalloc(nPoints * sizeof(Point2d)); + orig = (Point2d *)vPtr->valueArr; + n = Blt_SimplifyLine(orig, 0, nPoints - 1, tolerance, simple); + for (i = 0; i < n; i++) { + reduced[i] = orig[simple[i]]; + } + Blt_Free(simple); + Blt_Vec_Reset(vPtr, (double *)reduced, n * 2, vPtr->length, TCL_DYNAMIC); + /* + * The vector has changed; so flush the array indices (they're wrong + * now), find the new range of the data, and notify the vector's + * clients that it's been modified. + */ + if (vPtr->flush) { + Blt_Vec_FlushCache(vPtr); + } + Blt_Vec_UpdateClients(vPtr); + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * SplitOp -- + * + * Copies the values from the vector evenly into one of more vectors. + * + * Results: + * A standard TCL result. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +SplitOp(Vector *vPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) +{ + int nVectors; + + nVectors = objc - 2; + if ((vPtr->length % nVectors) != 0) { + Tcl_AppendResult(interp, "can't split vector \"", vPtr->name, + "\" into ", Blt_Itoa(nVectors), " even parts.", (char *)NULL); + return TCL_ERROR; + } + if (nVectors > 0) { + Vector *v2Ptr; + char *string; /* Name of vector. */ + int i, j, k; + int oldSize, newSize, extra, isNew; + + extra = vPtr->length / nVectors; + for (i = 0; i < nVectors; i++) { + string = Tcl_GetString(objv[i+2]); + v2Ptr = Blt_Vec_Create(vPtr->dataPtr, string, string, string, + &isNew); + oldSize = v2Ptr->length; + newSize = oldSize + extra; + if (Blt_Vec_SetLength(interp, v2Ptr, newSize) != TCL_OK) { + return TCL_ERROR; + } + for (j = i, k = oldSize; j < vPtr->length; j += nVectors, k++) { + v2Ptr->valueArr[k] = vPtr->valueArr[j]; + } + Blt_Vec_UpdateClients(v2Ptr); + if (v2Ptr->flush) { + Blt_Vec_FlushCache(v2Ptr); + } + } + } + return TCL_OK; +} + + +static Vector **sortVectors; /* Pointer to the array of values currently + * being sorted. */ +static int nSortVectors; +static int sortDecreasing; /* Indicates the ordering of the sort. If + * non-zero, the vectors are sorted in + * decreasing order */ + +static int +CompareVectors(void *a, void *b) +{ + double delta; + int i; + int sign; + Vector *vPtr; + + sign = (sortDecreasing) ? -1 : 1; + for (i = 0; i < nSortVectors; i++) { + vPtr = sortVectors[i]; + delta = vPtr->valueArr[*(int *)a] - vPtr->valueArr[*(int *)b]; + if (delta < 0.0) { + return (-1 * sign); + } else if (delta > 0.0) { + return (1 * sign); + } + } + return 0; +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_Vec_SortMap -- + * + * Returns an array of indices that represents the sorted mapping of the + * original vector. + * + * Results: + * A standard TCL result. If any of the auxiliary vectors are a + * different size than the sorted vector object, TCL_ERROR is returned. + * Otherwise TCL_OK is returned. + * + * Side Effects: + * The vectors are sorted. + * + * vecName sort ?switches? vecName vecName... + *--------------------------------------------------------------------------- + */ +size_t * +Blt_Vec_SortMap(Vector **vectors, int nVectors) +{ + size_t *map; + int i; + Vector *vPtr = *vectors; + int length; + + length = vPtr->last - vPtr->first + 1; + map = Blt_AssertMalloc(sizeof(size_t) * length); + for (i = vPtr->first; i <= vPtr->last; i++) { + map[i] = i; + } + /* Set global variables for sorting routine. */ + sortVectors = vectors; + nSortVectors = nVectors; + qsort((char *)map, length, sizeof(size_t), + (QSortCompareProc *)CompareVectors); + return map; +} + +static size_t * +SortVectors(Vector *vPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) +{ + Vector **vectors, *v2Ptr; + size_t *map; + int i; + + vectors = Blt_AssertMalloc(sizeof(Vector *) * (objc + 1)); + vectors[0] = vPtr; + map = NULL; + for (i = 0; i < objc; i++) { + if (Blt_Vec_LookupName(vPtr->dataPtr, Tcl_GetString(objv[i]), + &v2Ptr) != TCL_OK) { + goto error; + } + if (v2Ptr->length != vPtr->length) { + Tcl_AppendResult(interp, "vector \"", v2Ptr->name, + "\" is not the same size as \"", vPtr->name, "\"", + (char *)NULL); + goto error; + } + vectors[i + 1] = v2Ptr; + } + map = Blt_Vec_SortMap(vectors, objc + 1); + error: + Blt_Free(vectors); + return map; +} + + +/* + *--------------------------------------------------------------------------- + * + * SortOp -- + * + * Sorts the vector object and any other vectors according to sorting + * order of the vector object. + * + * Results: + * A standard TCL result. If any of the auxiliary vectors are a + * different size than the sorted vector object, TCL_ERROR is returned. + * Otherwise TCL_OK is returned. + * + * Side Effects: + * The vectors are sorted. + * + * vecName sort ?switches? vecName vecName... + *--------------------------------------------------------------------------- + */ +static int +SortOp(Vector *vPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) +{ + Vector *v2Ptr; + double *copy; + size_t *map; + size_t sortLength, nBytes; + int result; + int i; + unsigned int n; + SortSwitches switches; + + sortDecreasing = FALSE; + switches.flags = 0; + i = Blt_ParseSwitches(interp, sortSwitches, objc - 2, objv + 2, &switches, + BLT_SWITCH_OBJV_PARTIAL); + if (i < 0) { + return TCL_ERROR; + } + objc -= i, objv += i; + sortDecreasing = (switches.flags & SORT_DECREASING); + if (objc > 2) { + map = SortVectors(vPtr, interp, objc - 2, objv + 2); + } else { + map = Blt_Vec_SortMap(&vPtr, 1); + } + if (map == NULL) { + return TCL_ERROR; + } + sortLength = vPtr->length; + /* + * Create an array to store a copy of the current values of the + * vector. We'll merge the values back into the vector based upon the + * indices found in the index array. + */ + nBytes = sizeof(double) * sortLength; + copy = Blt_AssertMalloc(nBytes); + memcpy((char *)copy, (char *)vPtr->valueArr, nBytes); + if (switches.flags & SORT_UNIQUE) { + int count; + + for (count = n = 1; n < sortLength; n++) { + size_t next, prev; + + next = map[n]; + prev = map[n - 1]; + if (copy[next] != copy[prev]) { + map[count] = next; + count++; + } + } + sortLength = count; + nBytes = sortLength * sizeof(double); + } + if (sortLength != vPtr->length) { + Blt_Vec_SetLength(interp, vPtr, sortLength); + } + for (n = 0; n < sortLength; n++) { + vPtr->valueArr[n] = copy[map[n]]; + } + if (vPtr->flush) { + Blt_Vec_FlushCache(vPtr); + } + Blt_Vec_UpdateClients(vPtr); + + /* Now sort any other vectors in the same fashion. The vectors must be + * the same size as the map though. */ + result = TCL_ERROR; + for (i = 2; i < objc; i++) { + if (Blt_Vec_LookupName(vPtr->dataPtr, Tcl_GetString(objv[i]), + &v2Ptr) != TCL_OK) { + goto error; + } + if (sortLength != v2Ptr->length) { + Blt_Vec_SetLength(interp, v2Ptr, sortLength); + } + memcpy((char *)copy, (char *)v2Ptr->valueArr, nBytes); + for (n = 0; n < sortLength; n++) { + v2Ptr->valueArr[n] = copy[map[n]]; + } + Blt_Vec_UpdateClients(v2Ptr); + if (v2Ptr->flush) { + Blt_Vec_FlushCache(v2Ptr); + } + } + result = TCL_OK; + error: + Blt_Free(copy); + Blt_Free(map); + return result; +} + +/* + *--------------------------------------------------------------------------- + * + * InstExprOp -- + * + * Computes the result of the expression which may be either a scalar + * (single value) or vector (list of values). + * + * Results: + * A standard TCL result. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +InstExprOp(Vector *vPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) +{ + + if (Blt_ExprVector(interp, Tcl_GetString(objv[2]), (Blt_Vector *)vPtr) + != TCL_OK) { + return TCL_ERROR; + } + if (vPtr->flush) { + Blt_Vec_FlushCache(vPtr); + } + Blt_Vec_UpdateClients(vPtr); + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * ArithOp -- + * + * Results: + * A standard TCL result. If the source vector doesn't exist or the + * source list is not a valid list of numbers, TCL_ERROR returned. + * Otherwise TCL_OK is returned. + * + * Side Effects: + * The vector data is reset. Clients of the vector are notified. + * Any cached array indices are flushed. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +ArithOp(Vector *vPtr, Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) +{ + double value; + int i; + Vector *v2Ptr; + double scalar; + Tcl_Obj *listObjPtr; + char *string; + + v2Ptr = Blt_Vec_ParseElement((Tcl_Interp *)NULL, vPtr->dataPtr, + Tcl_GetString(objv[2]), NULL, NS_SEARCH_BOTH); + if (v2Ptr != NULL) { + int j; + int length; + + length = v2Ptr->last - v2Ptr->first + 1; + if (length != vPtr->length) { + Tcl_AppendResult(interp, "vectors \"", Tcl_GetString(objv[0]), + "\" and \"", Tcl_GetString(objv[2]), + "\" are not the same length", (char *)NULL); + return TCL_ERROR; + } + string = Tcl_GetString(objv[1]); + listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL); + switch (string[0]) { + case '*': + for (i = 0, j = v2Ptr->first; i < vPtr->length; i++, j++) { + value = vPtr->valueArr[i] * v2Ptr->valueArr[j]; + Tcl_ListObjAppendElement(interp, listObjPtr, + Tcl_NewDoubleObj(value)); + } + break; + + case '/': + for (i = 0, j = v2Ptr->first; i < vPtr->length; i++, j++) { + value = vPtr->valueArr[i] / v2Ptr->valueArr[j]; + Tcl_ListObjAppendElement(interp, listObjPtr, + Tcl_NewDoubleObj(value)); + } + break; + + case '-': + for (i = 0, j = v2Ptr->first; i < vPtr->length; i++, j++) { + value = vPtr->valueArr[i] - v2Ptr->valueArr[j]; + Tcl_ListObjAppendElement(interp, listObjPtr, + Tcl_NewDoubleObj(value)); + } + break; + + case '+': + for (i = 0, j = v2Ptr->first; i < vPtr->length; i++, j++) { + value = vPtr->valueArr[i] + v2Ptr->valueArr[j]; + Tcl_ListObjAppendElement(interp, listObjPtr, + Tcl_NewDoubleObj(value)); + } + break; + } + Tcl_SetObjResult(interp, listObjPtr); + + } else if (Blt_ExprDoubleFromObj(interp, objv[2], &scalar) == TCL_OK) { + listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL); + string = Tcl_GetString(objv[1]); + switch (string[0]) { + case '*': + for (i = 0; i < vPtr->length; i++) { + value = vPtr->valueArr[i] * scalar; + Tcl_ListObjAppendElement(interp, listObjPtr, + Tcl_NewDoubleObj(value)); + } + break; + + case '/': + for (i = 0; i < vPtr->length; i++) { + value = vPtr->valueArr[i] / scalar; + Tcl_ListObjAppendElement(interp, listObjPtr, + Tcl_NewDoubleObj(value)); + } + break; + + case '-': + for (i = 0; i < vPtr->length; i++) { + value = vPtr->valueArr[i] - scalar; + Tcl_ListObjAppendElement(interp, listObjPtr, + Tcl_NewDoubleObj(value)); + } + break; + + case '+': + for (i = 0; i < vPtr->length; i++) { + value = vPtr->valueArr[i] + scalar; + Tcl_ListObjAppendElement(interp, listObjPtr, + Tcl_NewDoubleObj(value)); + } + break; + } + Tcl_SetObjResult(interp, listObjPtr); + } else { + return TCL_ERROR; + } + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * VectorInstCmd -- + * + * Parses and invokes the appropriate vector instance command option. + * + * Results: + * A standard TCL result. + * + *--------------------------------------------------------------------------- + */ +static Blt_OpSpec vectorInstOps[] = +{ + {"*", 1, ArithOp, 3, 3, "item",}, /*Deprecated*/ + {"+", 1, ArithOp, 3, 3, "item",}, /*Deprecated*/ + {"-", 1, ArithOp, 3, 3, "item",}, /*Deprecated*/ + {"/", 1, ArithOp, 3, 3, "item",}, /*Deprecated*/ + {"append", 1, AppendOp, 3, 0, "item ?item...?",}, + {"binread", 1, BinreadOp, 3, 0, "channel ?numValues? ?flags?",}, + {"clear", 1, ClearOp, 2, 2, "",}, + {"delete", 2, DeleteOp, 2, 0, "index ?index...?",}, + {"dup", 2, DupOp, 3, 0, "vecName",}, + {"expr", 1, InstExprOp, 3, 3, "expression",}, + {"fft", 1, FFTOp, 3, 0, "vecName ?switches?",}, + {"index", 3, IndexOp, 3, 4, "index ?value?",}, + {"inversefft",3, InverseFFTOp,4, 4, "vecName vecName",}, + {"length", 1, LengthOp, 2, 3, "?newSize?",}, + {"max", 2, MaxOp, 2, 2, "",}, + {"merge", 2, MergeOp, 3, 0, "vecName ?vecName...?",}, + {"min", 2, MinOp, 2, 2, "",}, + {"normalize", 3, NormalizeOp, 2, 3, "?vecName?",}, /*Deprecated*/ + {"notify", 3, NotifyOp, 3, 3, "keyword",}, + {"offset", 1, OffsetOp, 2, 3, "?offset?",}, + {"populate", 1, PopulateOp, 4, 4, "vecName density",}, + {"random", 4, RandomOp, 2, 2, "",}, /*Deprecated*/ + {"range", 4, RangeOp, 2, 4, "first last",}, + {"search", 3, SearchOp, 3, 5, "?-value? value ?value?",}, + {"seq", 3, SeqOp, 4, 5, "begin end ?num?",}, + {"set", 3, SetOp, 3, 3, "list",}, + {"simplify", 2, SimplifyOp, 2, 2, }, + {"sort", 2, SortOp, 2, 0, "?switches? ?vecName...?",}, + {"split", 2, SplitOp, 2, 0, "?vecName...?",}, + {"values", 3, ValuesOp, 2, 0, "?switches?",}, + {"variable", 3, MapOp, 2, 3, "?varName?",}, +}; + +static int nInstOps = sizeof(vectorInstOps) / sizeof(Blt_OpSpec); + +int +Blt_Vec_InstCmd(ClientData clientData, Tcl_Interp *interp, int objc, + Tcl_Obj *const *objv) +{ + VectorCmdProc *proc; + Vector *vPtr = clientData; + + vPtr->first = 0; + vPtr->last = vPtr->length - 1; + proc = Blt_GetOpFromObj(interp, nInstOps, vectorInstOps, BLT_OP_ARG1, objc, + objv, 0); + if (proc == NULL) { + return TCL_ERROR; + } + return (*proc) (vPtr, interp, objc, objv); +} + + +/* + *--------------------------------------------------------------------------- + * + * Blt_Vec_VarTrace -- + * + * Results: + * Returns NULL on success. Only called from a variable trace. + * + * Side effects: + * + *--------------------------------------------------------------------------- + */ +char * +Blt_Vec_VarTrace(ClientData clientData, Tcl_Interp *interp, const char *part1, + const char *part2, int flags) +{ + Blt_VectorIndexProc *indexProc; + Vector *vPtr = clientData; + int first, last; + int varFlags; +#define MAX_ERR_MSG 1023 + static char message[MAX_ERR_MSG + 1]; + + if (part2 == NULL) { + if (flags & TCL_TRACE_UNSETS) { + Blt_Free(vPtr->arrayName); + vPtr->arrayName = NULL; + if (vPtr->freeOnUnset) { + Blt_Vec_Free(vPtr); + } + } + return NULL; + } + if (Blt_Vec_GetIndexRange(interp, vPtr, part2, INDEX_ALL_FLAGS, &indexProc) + != TCL_OK) { + goto error; + } + first = vPtr->first, last = vPtr->last; + varFlags = TCL_LEAVE_ERR_MSG | (TCL_GLOBAL_ONLY & flags); + if (flags & TCL_TRACE_WRITES) { + double value; + Tcl_Obj *objPtr; + + if (first == SPECIAL_INDEX) { /* Tried to set "min" or "max" */ + return (char *)"read-only index"; + } + objPtr = Tcl_GetVar2Ex(interp, part1, part2, varFlags); + if (objPtr == NULL) { + goto error; + } + if (Blt_ExprDoubleFromObj(interp, objPtr, &value) != TCL_OK) { + if ((last == first) && (first >= 0)) { + /* Single numeric index. Reset the array element to + * its old value on errors */ + Tcl_SetVar2Ex(interp, part1, part2, objPtr, varFlags); + } + goto error; + } + if (first == vPtr->length) { + if (Blt_Vec_ChangeLength((Tcl_Interp *)NULL, vPtr, vPtr->length + 1) + != TCL_OK) { + return (char *)"error resizing vector"; + } + } + /* Set possibly an entire range of values */ + ReplicateValue(vPtr, first, last, value); + } else if (flags & TCL_TRACE_READS) { + double value; + Tcl_Obj *objPtr; + + if (vPtr->length == 0) { + if (Tcl_SetVar2(interp, part1, part2, "", varFlags) == NULL) { + goto error; + } + return NULL; + } + if (first == vPtr->length) { + return (char *)"write-only index"; + } + if (first == last) { + if (first >= 0) { + value = vPtr->valueArr[first]; + } else { + vPtr->first = 0, vPtr->last = vPtr->length - 1; + value = (*indexProc) ((Blt_Vector *) vPtr); + } + objPtr = Tcl_NewDoubleObj(value); + if (Tcl_SetVar2Ex(interp, part1, part2, objPtr, varFlags) == NULL) { + Tcl_DecrRefCount(objPtr); + goto error; + } + } else { + objPtr = GetValues(vPtr, first, last); + if (Tcl_SetVar2Ex(interp, part1, part2, objPtr, varFlags) == NULL) { + Tcl_DecrRefCount(objPtr); + goto error; + } + } + } else if (flags & TCL_TRACE_UNSETS) { + int i, j; + + if ((first == vPtr->length) || (first == SPECIAL_INDEX)) { + return (char *)"special vector index"; + } + /* + * Collapse the vector from the point of the first unset element. + * Also flush any array variable entries so that the shift is + * reflected when the array variable is read. + */ + for (i = first, j = last + 1; j < vPtr->length; i++, j++) { + vPtr->valueArr[i] = vPtr->valueArr[j]; + } + vPtr->length -= ((last - first) + 1); + if (vPtr->flush) { + Blt_Vec_FlushCache(vPtr); + } + } else { + return (char *)"unknown variable trace flag"; + } + if (flags & (TCL_TRACE_UNSETS | TCL_TRACE_WRITES)) { + Blt_Vec_UpdateClients(vPtr); + } + Tcl_ResetResult(interp); + return NULL; + + error: + strncpy(message, Tcl_GetStringResult(interp), MAX_ERR_MSG); + message[MAX_ERR_MSG] = '\0'; + return message; +} diff --git a/src/bltVecInt.h b/src/bltVecInt.h new file mode 100644 index 0000000..5d30f97 --- /dev/null +++ b/src/bltVecInt.h @@ -0,0 +1,251 @@ + +/* + * bltVecInt.h -- + * + * Copyright 1995-2004 George A Howlett. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + + +#include "bltInt.h" +#include <bltHash.h> +#include <bltChain.h> +#include <bltVector.h> + +#define VECTOR_THREAD_KEY "BLT Vector Data" +#define VECTOR_MAGIC ((unsigned int) 0x46170277) + +/* These defines allow parsing of different types of indices */ + +#define INDEX_SPECIAL (1<<0) /* Recognize "min", "max", and "++end" as + * valid indices */ +#define INDEX_COLON (1<<1) /* Also recognize a range of indices separated + * by a colon */ +#define INDEX_CHECK (1<<2) /* Verify that the specified index or range of + * indices are within limits */ +#define INDEX_ALL_FLAGS (INDEX_SPECIAL | INDEX_COLON | INDEX_CHECK) + +#define SPECIAL_INDEX -2 + +#define FFT_NO_CONSTANT (1<<0) +#define FFT_BARTLETT (1<<1) +#define FFT_SPECTRUM (1<<2) + +typedef struct { + Blt_HashTable vectorTable; /* Table of vectors */ + Blt_HashTable mathProcTable; /* Table of vector math functions */ + Blt_HashTable indexProcTable; + Tcl_Interp *interp; + unsigned int nextId; +} VectorInterpData; + +/* + * Vector -- + * + * A vector is an array of double precision values. It can be accessed + * through a TCL command, a TCL array variable, or C API. The storage for + * the array points initially to a statically allocated buffer, but to + * malloc-ed memory if more is necessary. + * + * Vectors can be shared by several clients (for example, two different + * graph widgets). The data is shared. When a client wants to use a + * vector, it allocates a vector identifier, which identifies the client. + * Clients use this ID to specify a callback routine to be invoked + * whenever the vector is modified or destroyed. Whenever the vector is + * updated or destroyed, each client is notified of the change by their + * callback routine. + */ + +typedef struct { + + /* + * If you change these fields, make sure you change the definition of + * Blt_Vector in bltInt.h and blt.h too. + */ + + double *valueArr; /* Array of values (malloc-ed) */ + + int length; /* Current number of values in the array. */ + + int size; /* Maximum number of values that can be stored + * in the value array. */ + + double min, max; /* Minimum and maximum values in the vector */ + + int dirty; /* Indicates if the vector has been updated */ + + int reserved; + + /* The following fields are local to this module */ + + const char *name; /* The namespace-qualified name of the vector. + * It points to the hash key allocated for the + * entry in the vector hash table. */ + + VectorInterpData *dataPtr; + Tcl_Interp *interp; /* Interpreter associated with the + * vector */ + + Blt_HashEntry *hashPtr; /* If non-NULL, pointer in a hash table to + * track the vectors in use. */ + + Tcl_FreeProc *freeProc; /* Address of procedure to call to release + * storage for the value array, Optionally can + * be one of the following: TCL_STATIC, + * TCL_DYNAMIC, or TCL_VOLATILE. */ + + const char *arrayName; /* The name of the TCL array variable mapped + * to the vector (malloc'ed). If NULL, + * indicates that the vector isn't mapped to + * any variable */ + + Tcl_Namespace *nsPtr; /* Namespace context of the vector itself. */ + + int offset; /* Offset from zero of the vector's starting + * index */ + + Tcl_Command cmdToken; /* Token for vector's TCL command. */ + + Blt_Chain chain; /* List of clients using this vector */ + + int notifyFlags; /* Notification flags. See definitions + * below */ + + int varFlags; /* Indicate if the variable is global, + * namespace, or local */ + + int freeOnUnset; /* For backward compatibility only: If + * non-zero, free the vector when its variable + * is unset. */ + int flush; + + int first, last; /* Selected region of vector. This is used + * mostly for the math routines */ +} Vector; + +#define NOTIFY_UPDATED ((int)BLT_VECTOR_NOTIFY_UPDATE) +#define NOTIFY_DESTROYED ((int)BLT_VECTOR_NOTIFY_DESTROY) + +#define NOTIFY_NEVER (1<<3) /* Never notify clients of updates to + * the vector */ +#define NOTIFY_ALWAYS (1<<4) /* Notify clients after each update + * of the vector is made */ +#define NOTIFY_WHENIDLE (1<<5) /* Notify clients at the next idle point + * that the vector has been updated. */ + +#define NOTIFY_PENDING (1<<6) /* A do-when-idle notification of the + * vector's clients is pending. */ +#define NOTIFY_NOW (1<<7) /* Notify clients of changes once + * immediately */ + +#define NOTIFY_WHEN_MASK (NOTIFY_NEVER|NOTIFY_ALWAYS|NOTIFY_WHENIDLE) + +#define UPDATE_RANGE (1<<9) /* The data of the vector has changed. + * Update the min and max limits when + * they are needed */ + +#define FindRange(array, first, last, min, max) \ +{ \ + min = max = 0.0; \ + if (first <= last) { \ + register int i; \ + min = max = array[first]; \ + for (i = first + 1; i <= last; i++) { \ + if (min > array[i]) { \ + min = array[i]; \ + } else if (max < array[i]) { \ + max = array[i]; \ + } \ + } \ + } \ +} + +BLT_EXTERN void Blt_Vec_InstallSpecialIndices(Blt_HashTable *tablePtr); + +BLT_EXTERN void Blt_Vec_InstallMathFunctions(Blt_HashTable *tablePtr); + +BLT_EXTERN void Blt_Vec_UninstallMathFunctions(Blt_HashTable *tablePtr); + +BLT_EXTERN VectorInterpData *Blt_Vec_GetInterpData (Tcl_Interp *interp); + +BLT_EXTERN double Blt_Vec_Max(Vector *vecObjPtr); +BLT_EXTERN double Blt_Vec_Min(Vector *vecObjPtr); + +BLT_EXTERN Vector *Blt_Vec_New(VectorInterpData *dataPtr); + +BLT_EXTERN int Blt_Vec_Duplicate(Vector *destPtr, Vector *srcPtr); + +BLT_EXTERN int Blt_Vec_SetLength(Tcl_Interp *interp, Vector *vPtr, + int length); + +BLT_EXTERN int Blt_Vec_SetSize(Tcl_Interp *interp, Vector *vPtr, + int size); + +BLT_EXTERN int Blt_Vec_ChangeLength(Tcl_Interp *interp, Vector *vPtr, + int length); + +BLT_EXTERN Vector *Blt_Vec_ParseElement(Tcl_Interp *interp, + VectorInterpData *dataPtr, const char *start, const char **endPtr, + int flags); + +BLT_EXTERN void Blt_Vec_Free(Vector *vPtr); + +BLT_EXTERN size_t *Blt_Vec_SortMap(Vector **vectors, int nVectors); + +BLT_EXTERN int Blt_Vec_LookupName(VectorInterpData *dataPtr, + const char *vecName, Vector **vPtrPtr); + +BLT_EXTERN Vector *Blt_Vec_Create(VectorInterpData *dataPtr, + const char *name, const char *cmdName, const char *varName, + int *newPtr); + +BLT_EXTERN void Blt_Vec_UpdateRange(Vector *vPtr); + +BLT_EXTERN void Blt_Vec_UpdateClients(Vector *vPtr); + +BLT_EXTERN void Blt_Vec_FlushCache(Vector *vPtr); + +BLT_EXTERN int Blt_Vec_Reset(Vector *vPtr, double *dataArr, + int nValues, int arraySize, Tcl_FreeProc *freeProc); + +BLT_EXTERN int Blt_Vec_GetIndex(Tcl_Interp *interp, Vector *vPtr, + const char *string, int *indexPtr, int flags, + Blt_VectorIndexProc **procPtrPtr); + +BLT_EXTERN int Blt_Vec_GetIndexRange(Tcl_Interp *interp, Vector *vPtr, + const char *string, int flags, Blt_VectorIndexProc **procPtrPtr); + +BLT_EXTERN int Blt_Vec_MapVariable(Tcl_Interp *interp, Vector *vPtr, + const char *name); + +BLT_EXTERN int Blt_Vec_FFT(Tcl_Interp *interp, Vector *realPtr, + Vector *phasesPtr, Vector *freqPtr, double delta, + int flags, Vector *srcPtr); + +BLT_EXTERN int Blt_Vec_InverseFFT(Tcl_Interp *interp, Vector *iSrcPtr, + Vector *rDestPtr, Vector *iDestPtr, Vector *srcPtr); + +BLT_EXTERN Tcl_ObjCmdProc Blt_Vec_InstCmd; + +BLT_EXTERN Tcl_VarTraceProc Blt_Vec_VarTrace; + +BLT_EXTERN Tcl_IdleProc Blt_Vec_NotifyClients; + diff --git a/src/bltVecMath.C b/src/bltVecMath.C new file mode 100644 index 0000000..a0bd4af --- /dev/null +++ b/src/bltVecMath.C @@ -0,0 +1,1895 @@ + +/* + * bltVecMath.c -- + * + * This module implements mathematical expressions with vector data + * objects. + * + * Copyright 1995-2004 George A Howlett. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY + * KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS + * OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include "bltVecInt.h" +#include "bltNsUtil.h" +#include "bltParse.h" + +/* + * Three types of math functions: + * + * ComponentProc Function is applied in multiple calls to + * each component of the vector. + * VectorProc Entire vector is passed, each component is + * modified. + * ScalarProc Entire vector is passed, single scalar value + * is returned. + */ + +typedef double (ComponentProc)(double value); +typedef int (VectorProc)(Vector *vPtr); +typedef double (ScalarProc)(Vector *vPtr); + +/* + * Built-in math functions: + */ +typedef int (GenericMathProc) _ANSI_ARGS_(ANYARGS); + +/* + * MathFunction -- + * + * Contains information about math functions that can be called + * for vectors. The table of math functions is global within the + * application. So you can't define two different "sqrt" + * functions. + */ +typedef struct { + const char *name; /* Name of built-in math function. If + * NULL, indicates that the function + * was user-defined and dynamically + * allocated. Function names are + * global across all interpreters. */ + + void *proc; /* Procedure that implements this math + * function. */ + + ClientData clientData; /* Argument to pass when invoking the + * function. */ + +} MathFunction; + + +/* + * Macros for testing floating-point values for certain special cases: + * + * IS_NAN Test for not-a-number by comparing a value against itself + * IF_INF Test for infinity by comparing against the largest floating + * point value. + */ + +#define IS_NAN(v) ((v) != (v)) + +#ifdef DBL_MAX +# define IS_INF(v) (((v) > DBL_MAX) || ((v) < -DBL_MAX)) +#else +# define IS_INF(v) 0 +#endif + +/* The data structure below is used to describe an expression value, + * which can be either a double-precision floating-point value, or a + * string. A given number has only one value at a time. */ + +#define STATIC_STRING_SPACE 150 + +/* + * Tokens -- + * + * The token types are defined below. In addition, there is a + * table associating a precedence with each operator. The order + * of types is important. Consult the code before changing it. + */ +enum Tokens { + VALUE, OPEN_PAREN, CLOSE_PAREN, COMMA, END, UNKNOWN, + MULT = 8, DIVIDE, MOD, PLUS, MINUS, + LEFT_SHIFT, RIGHT_SHIFT, + LESS, GREATER, LEQ, GEQ, EQUAL, NEQ, + OLD_BIT_AND, EXPONENT, OLD_BIT_OR, OLD_QUESTY, OLD_COLON, + AND, OR, UNARY_MINUS, OLD_UNARY_PLUS, NOT, OLD_BIT_NOT +}; + +typedef struct { + Vector *vPtr; + char staticSpace[STATIC_STRING_SPACE]; + ParseValue pv; /* Used to hold a string value, if any. */ +} Value; + +/* + * ParseInfo -- + * + * The data structure below describes the state of parsing an + * expression. It's passed among the routines in this module. + */ +typedef struct { + const char *expr; /* The entire right-hand side of the + * expression, as originally passed to + * Blt_ExprVector. */ + + const char *nextPtr; /* Position of the next character to + * be scanned from the expression + * string. */ + + enum Tokens token; /* Type of the last token to be parsed + * from nextPtr. See below for + * definitions. Corresponds to the + * characters just before nextPtr. */ + +} ParseInfo; + +/* + * Precedence table. The values for non-operator token types are ignored. + */ +static int precTable[] = +{ + 0, 0, 0, 0, 0, 0, 0, 0, + 12, 12, 12, /* MULT, DIVIDE, MOD */ + 11, 11, /* PLUS, MINUS */ + 10, 10, /* LEFT_SHIFT, RIGHT_SHIFT */ + 9, 9, 9, 9, /* LESS, GREATER, LEQ, GEQ */ + 8, 8, /* EQUAL, NEQ */ + 7, /* OLD_BIT_AND */ + 13, /* EXPONENTIATION */ + 5, /* OLD_BIT_OR */ + 4, /* AND */ + 3, /* OR */ + 2, /* OLD_QUESTY */ + 1, /* OLD_COLON */ + 14, 14, 14, 14 /* UNARY_MINUS, OLD_UNARY_PLUS, NOT, + * OLD_BIT_NOT */ +}; + + +/* + * Forward declarations. + */ + +static int NextValue(Tcl_Interp *interp, ParseInfo *piPtr, int prec, + Value *valuePtr); + +#include <bltMath.h> + +/* + *--------------------------------------------------------------------------- + * + * Sort -- + * + * A vector math function. Sorts the values of the given + * vector. + * + * Results: + * Always TCL_OK. + * + * Side Effects: + * The vector is sorted. + * + *--------------------------------------------------------------------------- + */ +static int +Sort(Vector *vPtr) +{ + size_t *map; + double *values; + int i; + + map = Blt_Vec_SortMap(&vPtr, 1); + values = Blt_AssertMalloc(sizeof(double) * vPtr->length); + for(i = vPtr->first; i <= vPtr->last; i++) { + values[i] = vPtr->valueArr[map[i]]; + } + Blt_Free(map); + for (i = vPtr->first; i <= vPtr->last; i++) { + vPtr->valueArr[i] = values[i]; + } + Blt_Free(values); + return TCL_OK; +} + +static double +Length(Blt_Vector *vectorPtr) +{ + Vector *vPtr = (Vector *)vectorPtr; + + return (double)(vPtr->last - vPtr->first + 1); +} + +double +Blt_VecMax(Blt_Vector *vectorPtr) +{ + Vector *vPtr = (Vector *)vectorPtr; + + return Blt_Vec_Max(vPtr); +} + +double +Blt_VecMin(Blt_Vector *vectorPtr) +{ + Vector *vPtr = (Vector *)vectorPtr; + + return Blt_Vec_Min(vPtr); +} + + +static double +Product(Blt_Vector *vectorPtr) +{ + Vector *vPtr = (Vector *)vectorPtr; + double prod; + double *vp, *vend; + + prod = 1.0; + for(vp = vPtr->valueArr + vPtr->first, + vend = vPtr->valueArr + vPtr->last; vp <= vend; vp++) { + prod *= *vp; + } + return prod; +} + +static double +Sum(Blt_Vector *vectorPtr) +{ + Vector *vPtr = (Vector *)vectorPtr; + double sum, c; + double *vp, *vend; + + /* Kahan summation algorithm */ + + vp = vPtr->valueArr + vPtr->first; + sum = *vp++; + c = 0.0; /* A running compensation for lost + * low-order bits.*/ + for (vend = vPtr->valueArr + vPtr->last; vp <= vend; vp++) { + double y, t; + + y = *vp - c; /* So far, so good: c is zero.*/ + t = sum + y; /* Alas, sum is big, y small, so + * low-order digits of y are lost.*/ + c = (t - sum) - y; /* (t - sum) recovers the high-order + * part of y; subtracting y recovers + * -(low part of y) */ + sum = t; + } + return sum; +} + +static double +Mean(Blt_Vector *vectorPtr) +{ + Vector *vPtr = (Vector *)vectorPtr; + double sum; + int n; + + sum = Sum(vectorPtr); + n = vPtr->last - vPtr->first + 1; + return sum / (double)n; +} + +/* + * var = 1/N Sum( (x[i] - mean)^2 ) + */ +static double +Variance(Blt_Vector *vectorPtr) +{ + Vector *vPtr = (Vector *)vectorPtr; + double var, mean; + double *vp, *vend; + int count; + + mean = Mean(vectorPtr); + var = 0.0; + count = 0; + for(vp = vPtr->valueArr + vPtr->first, + vend = vPtr->valueArr + vPtr->last; vp <= vend; vp++) { + double dx; + + dx = *vp - mean; + var += dx * dx; + count++; + } + if (count < 2) { + return 0.0; + } + var /= (double)(count - 1); + return var; +} + +/* + * skew = Sum( (x[i] - mean)^3 ) / (var^3/2) + */ +static double +Skew(Blt_Vector *vectorPtr) +{ + Vector *vPtr = (Vector *)vectorPtr; + double diff, var, skew, mean, diffsq; + double *vp, *vend; + int count; + + mean = Mean(vectorPtr); + var = skew = 0.0; + count = 0; + for(vp = vPtr->valueArr + vPtr->first, + vend = vPtr->valueArr + vPtr->last; vp <= vend; vp++) { + diff = *vp - mean; + diff = FABS(diff); + diffsq = diff * diff; + var += diffsq; + skew += diffsq * diff; + count++; + } + if (count < 2) { + return 0.0; + } + var /= (double)(count - 1); + skew /= count * var * sqrt(var); + return skew; +} + +static double +StdDeviation(Blt_Vector *vectorPtr) +{ + double var; + + var = Variance(vectorPtr); + if (var > 0.0) { + return sqrt(var); + } + return 0.0; +} + + +static double +AvgDeviation(Blt_Vector *vectorPtr) +{ + Vector *vPtr = (Vector *)vectorPtr; + double diff, avg, mean; + double *vp, *vend; + int count; + + mean = Mean(vectorPtr); + avg = 0.0; + count = 0; + for(vp = vPtr->valueArr + vPtr->first, + vend = vPtr->valueArr + vPtr->last; vp <= vend; vp++) { + diff = *vp - mean; + avg += FABS(diff); + count++; + } + if (count < 2) { + return 0.0; + } + avg /= (double)count; + return avg; +} + + +static double +Kurtosis(Blt_Vector *vectorPtr) +{ + Vector *vPtr = (Vector *)vectorPtr; + double diff, diffsq, kurt, var, mean; + double *vp, *vend; + int count; + + mean = Mean(vectorPtr); + var = kurt = 0.0; + count = 0; + for(vp = vPtr->valueArr + vPtr->first, + vend = vPtr->valueArr + vPtr->last; vp <= vend; vp++) { + diff = *vp - mean; + diffsq = diff * diff; + var += diffsq; + kurt += diffsq * diffsq; + count++; + } + if (count < 2) { + return 0.0; + } + var /= (double)(count - 1); + if (var == 0.0) { + return 0.0; + } + kurt /= (count * var * var); + return kurt - 3.0; /* Fisher Kurtosis */ +} + + +static double +Median(Blt_Vector *vectorPtr) +{ + Vector *vPtr = (Vector *)vectorPtr; + size_t *map; + double q2; + int mid; + + if (vPtr->length == 0) { + return -DBL_MAX; + } + map = Blt_Vec_SortMap(&vPtr, 1); + mid = (vPtr->length - 1) / 2; + + /* + * Determine Q2 by checking if the number of elements [0..n-1] is + * odd or even. If even, we must take the average of the two + * middle values. + */ + if (vPtr->length & 1) { /* Odd */ + q2 = vPtr->valueArr[map[mid]]; + } else { /* Even */ + q2 = (vPtr->valueArr[map[mid]] + + vPtr->valueArr[map[mid + 1]]) * 0.5; + } + Blt_Free(map); + return q2; +} + +static double +Q1(Blt_Vector *vectorPtr) +{ + Vector *vPtr = (Vector *)vectorPtr; + double q1; + size_t *map; + + if (vPtr->length == 0) { + return -DBL_MAX; + } + map = Blt_Vec_SortMap(&vPtr, 1); + + if (vPtr->length < 4) { + q1 = vPtr->valueArr[map[0]]; + } else { + int mid, q; + + mid = (vPtr->length - 1) / 2; + q = mid / 2; + + /* + * Determine Q1 by checking if the number of elements in the + * bottom half [0..mid) is odd or even. If even, we must + * take the average of the two middle values. + */ + if (mid & 1) { /* Odd */ + q1 = vPtr->valueArr[map[q]]; + } else { /* Even */ + q1 = (vPtr->valueArr[map[q]] + + vPtr->valueArr[map[q + 1]]) * 0.5; + } + } + Blt_Free(map); + return q1; +} + +static double +Q3(Blt_Vector *vectorPtr) +{ + Vector *vPtr = (Vector *)vectorPtr; + double q3; + size_t *map; + + if (vPtr->length == 0) { + return -DBL_MAX; + } + + map = Blt_Vec_SortMap(&vPtr, 1); + + if (vPtr->length < 4) { + q3 = vPtr->valueArr[map[vPtr->length - 1]]; + } else { + int mid, q; + + mid = (vPtr->length - 1) / 2; + q = (vPtr->length + mid) / 2; + + /* + * Determine Q3 by checking if the number of elements in the + * upper half (mid..n-1] is odd or even. If even, we must + * take the average of the two middle values. + */ + if (mid & 1) { /* Odd */ + q3 = vPtr->valueArr[map[q]]; + } else { /* Even */ + q3 = (vPtr->valueArr[map[q]] + + vPtr->valueArr[map[q + 1]]) * 0.5; + } + } + Blt_Free(map); + return q3; +} + + +static int +Norm(Blt_Vector *vector) +{ + Vector *vPtr = (Vector *)vector; + double norm, range, min, max; + int i; + + min = Blt_Vec_Min(vPtr); + max = Blt_Vec_Max(vPtr); + range = max - min; + for (i = 0; i < vPtr->length; i++) { + norm = (vPtr->valueArr[i] - min) / range; + vPtr->valueArr[i] = norm; + } + return TCL_OK; +} + + +static double +Nonzeros(Blt_Vector *vector) +{ + Vector *vPtr = (Vector *)vector; + int count; + double *vp, *vend; + + count = 0; + for(vp = vPtr->valueArr + vPtr->first, + vend = vPtr->valueArr + vPtr->last; vp <= vend; vp++) { + if (*vp == 0.0) { + count++; + } + } + return (double) count; +} + +static double +Fabs(double value) +{ + if (value < 0.0) { + return -value; + } + return value; +} + +static double +Round(double value) +{ + if (value < 0.0) { + return ceil(value - 0.5); + } else { + return floor(value + 0.5); + } +} + +static double +Fmod(double x, double y) +{ + if (y == 0.0) { + return 0.0; + } + return x - (floor(x / y) * y); +} + +/* + *--------------------------------------------------------------------------- + * + * MathError -- + * + * This procedure is called when an error occurs during a + * floating-point operation. It reads errno and sets + * interp->result accordingly. + * + * Results: + * Interp->result is set to hold an error message. + * + * Side effects: + * None. + * + *--------------------------------------------------------------------------- + */ +static void +MathError( + Tcl_Interp *interp, /* Where to store error message. */ + double value) /* Value returned after error; used to + * distinguish underflows from + * overflows. */ +{ + if ((errno == EDOM) || (value != value)) { + Tcl_AppendResult(interp, "domain error: argument not in valid range", + (char *)NULL); + Tcl_SetErrorCode(interp, "ARITH", "DOMAIN", interp->result, + (char *)NULL); + } else if ((errno == ERANGE) || IS_INF(value)) { + if (value == 0.0) { + Tcl_AppendResult(interp, + "floating-point value too small to represent", + (char *)NULL); + Tcl_SetErrorCode(interp, "ARITH", "UNDERFLOW", interp->result, + (char *)NULL); + } else { + Tcl_AppendResult(interp, + "floating-point value too large to represent", + (char *)NULL); + Tcl_SetErrorCode(interp, "ARITH", "OVERFLOW", interp->result, + (char *)NULL); + } + } else { + Tcl_AppendResult(interp, "unknown floating-point error, ", + "errno = ", Blt_Itoa(errno), (char *)NULL); + Tcl_SetErrorCode(interp, "ARITH", "UNKNOWN", interp->result, + (char *)NULL); + } +} + +/* + *--------------------------------------------------------------------------- + * + * ParseString -- + * + * Given a string (such as one coming from command or variable + * substitution), make a Value based on the string. The value + * will be a floating-point or integer, if possible, or else it + * will just be a copy of the string. + * + * Results: + * TCL_OK is returned under normal circumstances, and TCL_ERROR + * is returned if a floating-point overflow or underflow occurred + * while reading in a number. The value at *valuePtr is modified + * to hold a number, if possible. + * + * Side effects: + * None. + * + *--------------------------------------------------------------------------- + */ + +static int +ParseString( + Tcl_Interp *interp, /* Where to store error message. */ + const char *string, /* String to turn into value. */ + Value *valuePtr) /* Where to store value information. + * Caller must have initialized pv field. */ +{ + const char *endPtr; + double value; + + errno = 0; + + /* + * The string can be either a number or a vector. First try to + * convert the string to a number. If that fails then see if + * we can find a vector by that name. + */ + + value = strtod(string, (char **)&endPtr); + if ((endPtr != string) && (*endPtr == '\0')) { + if (errno != 0) { + Tcl_ResetResult(interp); + MathError(interp, value); + return TCL_ERROR; + } + /* Numbers are stored as single element vectors. */ + if (Blt_Vec_ChangeLength(interp, valuePtr->vPtr, 1) != TCL_OK) { + return TCL_ERROR; + } + valuePtr->vPtr->valueArr[0] = value; + return TCL_OK; + } else { + Vector *vPtr; + + while (isspace(UCHAR(*string))) { + string++; /* Skip spaces leading the vector name. */ + } + vPtr = Blt_Vec_ParseElement(interp, valuePtr->vPtr->dataPtr, + string, &endPtr, NS_SEARCH_BOTH); + if (vPtr == NULL) { + return TCL_ERROR; + } + if (*endPtr != '\0') { + Tcl_AppendResult(interp, "extra characters after vector", + (char *)NULL); + return TCL_ERROR; + } + /* Copy the designated vector to our temporary. */ + Blt_Vec_Duplicate(valuePtr->vPtr, vPtr); + } + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * ParseMathFunction -- + * + * This procedure is invoked to parse a math function from an + * expression string, carry out the function, and return the + * value computed. + * + * Results: + * TCL_OK is returned if all went well and the function's value + * was computed successfully. If the name doesn't match any + * known math function, returns TCL_RETURN. And if a format error + * was found, TCL_ERROR is returned and an error message is left + * in interp->result. + * + * After a successful return piPtr will be updated to point to + * the character just after the function call, the token is set + * to VALUE, and the value is stored in valuePtr. + * + * Side effects: + * Embedded commands could have arbitrary side-effects. + * + *--------------------------------------------------------------------------- + */ +static int +ParseMathFunction( + Tcl_Interp *interp, /* Interpreter to use for error reporting. */ + const char *start, /* Start of string to parse */ + ParseInfo *piPtr, /* Describes the state of the parse. + * piPtr->nextPtr must point to the + * first character of the function's + * name. */ + Value *valuePtr) /* Where to store value, if that is + * what's parsed from string. Caller + * must have initialized pv field + * correctly. */ +{ + Blt_HashEntry *hPtr; + MathFunction *mathPtr; /* Info about math function. */ + char *p; + VectorInterpData *dataPtr; /* Interpreter-specific data. */ + GenericMathProc *proc; + + /* + * Find the end of the math function's name and lookup the + * record for the function. + */ + p = (char *)start; + while (isspace(UCHAR(*p))) { + p++; + } + piPtr->nextPtr = p; + while (isalnum(UCHAR(*p)) || (*p == '_')) { + p++; + } + if (*p != '(') { + return TCL_RETURN; /* Must start with open parenthesis */ + } + dataPtr = valuePtr->vPtr->dataPtr; + *p = '\0'; + hPtr = Blt_FindHashEntry(&dataPtr->mathProcTable, piPtr->nextPtr); + *p = '('; + if (hPtr == NULL) { + return TCL_RETURN; /* Name doesn't match any known function */ + } + /* Pick up the single value as the argument to the function */ + piPtr->token = OPEN_PAREN; + piPtr->nextPtr = p + 1; + valuePtr->pv.next = valuePtr->pv.buffer; + if (NextValue(interp, piPtr, -1, valuePtr) != TCL_OK) { + return TCL_ERROR; /* Parse error */ + } + if (piPtr->token != CLOSE_PAREN) { + Tcl_AppendResult(interp, "unmatched parentheses in expression \"", + piPtr->expr, "\"", (char *)NULL); + return TCL_ERROR; /* Missing right parenthesis */ + } + mathPtr = Blt_GetHashValue(hPtr); + proc = mathPtr->proc; + if ((*proc) (mathPtr->clientData, interp, valuePtr->vPtr) != TCL_OK) { + return TCL_ERROR; /* Function invocation error */ + } + piPtr->token = VALUE; + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * NextToken -- + * + * Lexical analyzer for expression parser: parses a single value, + * operator, or other syntactic element from an expression string. + * + * Results: + * TCL_OK is returned unless an error occurred while doing lexical + * analysis or executing an embedded command. In that case a + * standard TCL error is returned, using interp->result to hold + * an error message. In the event of a successful return, the token + * and field in piPtr is updated to refer to the next symbol in + * the expression string, and the expr field is advanced past that + * token; if the token is a value, then the value is stored at + * valuePtr. + * + * Side effects: + * None. + * + *--------------------------------------------------------------------------- + */ +static int +NextToken( + Tcl_Interp *interp, /* Interpreter to use for error reporting. */ + ParseInfo *piPtr, /* Describes the state of the parse. */ + Value *valuePtr) /* Where to store value, if that is + * what's parsed from string. Caller + * must have initialized pv field + * correctly. */ +{ + const char *p; + const char *endPtr; + const char *var; + int result; + + p = piPtr->nextPtr; + while (isspace(UCHAR(*p))) { + p++; + } + if (*p == '\0') { + piPtr->token = END; + piPtr->nextPtr = p; + return TCL_OK; + } + /* + * Try to parse the token as a floating-point number. But check + * that the first character isn't a "-" or "+", which "strtod" + * will happily accept as an unary operator. Otherwise, we might + * accidently treat a binary operator as unary by mistake, which + * will eventually cause a syntax error. + */ + if ((*p != '-') && (*p != '+')) { + double value; + + errno = 0; + value = strtod(p, (char **)&endPtr); + if (endPtr != p) { + if (errno != 0) { + MathError(interp, value); + return TCL_ERROR; + } + piPtr->token = VALUE; + piPtr->nextPtr = endPtr; + + /* + * Save the single floating-point value as an 1-component vector. + */ + if (Blt_Vec_ChangeLength(interp, valuePtr->vPtr, 1) != TCL_OK) { + return TCL_ERROR; + } + valuePtr->vPtr->valueArr[0] = value; + return TCL_OK; + } + } + piPtr->nextPtr = p + 1; + switch (*p) { + case '$': + piPtr->token = VALUE; + var = Tcl_ParseVar(interp, p, &endPtr); + if (var == NULL) { + return TCL_ERROR; + } + piPtr->nextPtr = endPtr; + Tcl_ResetResult(interp); + result = ParseString(interp, var, valuePtr); + return result; + + case '[': + piPtr->token = VALUE; + result = Blt_ParseNestedCmd(interp, p + 1, 0, &endPtr, &valuePtr->pv); + if (result != TCL_OK) { + return result; + } + piPtr->nextPtr = endPtr; + Tcl_ResetResult(interp); + result = ParseString(interp, valuePtr->pv.buffer, valuePtr); + return result; + + case '"': + piPtr->token = VALUE; + result = Blt_ParseQuotes(interp, p + 1, '"', 0, &endPtr, &valuePtr->pv); + if (result != TCL_OK) { + return result; + } + piPtr->nextPtr = endPtr; + Tcl_ResetResult(interp); + result = ParseString(interp, valuePtr->pv.buffer, valuePtr); + return result; + + case '{': + piPtr->token = VALUE; + result = Blt_ParseBraces(interp, p + 1, &endPtr, &valuePtr->pv); + if (result != TCL_OK) { + return result; + } + piPtr->nextPtr = endPtr; + Tcl_ResetResult(interp); + result = ParseString(interp, valuePtr->pv.buffer, valuePtr); + return result; + + case '(': + piPtr->token = OPEN_PAREN; + break; + + case ')': + piPtr->token = CLOSE_PAREN; + break; + + case ',': + piPtr->token = COMMA; + break; + + case '*': + piPtr->token = MULT; + break; + + case '/': + piPtr->token = DIVIDE; + break; + + case '%': + piPtr->token = MOD; + break; + + case '+': + piPtr->token = PLUS; + break; + + case '-': + piPtr->token = MINUS; + break; + + case '^': + piPtr->token = EXPONENT; + break; + + case '<': + switch (*(p + 1)) { + case '<': + piPtr->nextPtr = p + 2; + piPtr->token = LEFT_SHIFT; + break; + case '=': + piPtr->nextPtr = p + 2; + piPtr->token = LEQ; + break; + default: + piPtr->token = LESS; + break; + } + break; + + case '>': + switch (*(p + 1)) { + case '>': + piPtr->nextPtr = p + 2; + piPtr->token = RIGHT_SHIFT; + break; + case '=': + piPtr->nextPtr = p + 2; + piPtr->token = GEQ; + break; + default: + piPtr->token = GREATER; + break; + } + break; + + case '=': + if (*(p + 1) == '=') { + piPtr->nextPtr = p + 2; + piPtr->token = EQUAL; + } else { + piPtr->token = UNKNOWN; + } + break; + + case '&': + if (*(p + 1) == '&') { + piPtr->nextPtr = p + 2; + piPtr->token = AND; + } else { + piPtr->token = UNKNOWN; + } + break; + + case '|': + if (*(p + 1) == '|') { + piPtr->nextPtr = p + 2; + piPtr->token = OR; + } else { + piPtr->token = UNKNOWN; + } + break; + + case '!': + if (*(p + 1) == '=') { + piPtr->nextPtr = p + 2; + piPtr->token = NEQ; + } else { + piPtr->token = NOT; + } + break; + + default: + piPtr->token = VALUE; + result = ParseMathFunction(interp, p, piPtr, valuePtr); + if ((result == TCL_OK) || (result == TCL_ERROR)) { + return result; + } else { + Vector *vPtr; + + while (isspace(UCHAR(*p))) { + p++; /* Skip spaces leading the vector name. */ + } + vPtr = Blt_Vec_ParseElement(interp, valuePtr->vPtr->dataPtr, + p, &endPtr, NS_SEARCH_BOTH); + if (vPtr == NULL) { + return TCL_ERROR; + } + Blt_Vec_Duplicate(valuePtr->vPtr, vPtr); + piPtr->nextPtr = endPtr; + } + } + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * NextValue -- + * + * Parse a "value" from the remainder of the expression in piPtr. + * + * Results: + * Normally TCL_OK is returned. The value of the expression is + * returned in *valuePtr. If an error occurred, then interp->result + * contains an error message and TCL_ERROR is returned. + * InfoPtr->token will be left pointing to the token AFTER the + * expression, and piPtr->nextPtr will point to the character just + * after the terminating token. + * + * Side effects: + * None. + * + *--------------------------------------------------------------------------- + */ +static int +NextValue( + Tcl_Interp *interp, /* Interpreter to use for error reporting. */ + ParseInfo *piPtr, /* Describes the state of the parse + * just before the value (i.e. NextToken will + * be called to get first token of value). */ + int prec, /* Treat any un-parenthesized operator + * with precedence <= this as the end + * of the expression. */ + Value *valuePtr) /* Where to store the value of the expression. + * Caller must have initialized pv field. */ +{ + Value value2; /* Second operand for current operator. */ + int operator; /* Current operator (either unary or binary). */ + int gotOp; /* Non-zero means already lexed the operator + * (while picking up value for unary operator). + * Don't lex again. */ + int result; + Vector *vPtr, *v2Ptr; + int i; + + /* + * There are two phases to this procedure. First, pick off an initial + * value. Then, parse (binary operator, value) pairs until done. + */ + + vPtr = valuePtr->vPtr; + v2Ptr = Blt_Vec_New(vPtr->dataPtr); + gotOp = FALSE; + value2.vPtr = v2Ptr; + value2.pv.buffer = value2.pv.next = value2.staticSpace; + value2.pv.end = value2.pv.buffer + STATIC_STRING_SPACE - 1; + value2.pv.expandProc = Blt_ExpandParseValue; + value2.pv.clientData = NULL; + + result = NextToken(interp, piPtr, valuePtr); + if (result != TCL_OK) { + goto done; + } + if (piPtr->token == OPEN_PAREN) { + + /* Parenthesized sub-expression. */ + + result = NextValue(interp, piPtr, -1, valuePtr); + if (result != TCL_OK) { + goto done; + } + if (piPtr->token != CLOSE_PAREN) { + Tcl_AppendResult(interp, "unmatched parentheses in expression \"", + piPtr->expr, "\"", (char *)NULL); + result = TCL_ERROR; + goto done; + } + } else { + if (piPtr->token == MINUS) { + piPtr->token = UNARY_MINUS; + } + if (piPtr->token >= UNARY_MINUS) { + operator = piPtr->token; + result = NextValue(interp, piPtr, precTable[operator], valuePtr); + if (result != TCL_OK) { + goto done; + } + gotOp = TRUE; + /* Process unary operators. */ + switch (operator) { + case UNARY_MINUS: + for(i = 0; i < vPtr->length; i++) { + vPtr->valueArr[i] = -(vPtr->valueArr[i]); + } + break; + + case NOT: + for(i = 0; i < vPtr->length; i++) { + vPtr->valueArr[i] = (double)(!vPtr->valueArr[i]); + } + break; + default: + Tcl_AppendResult(interp, "unknown operator", (char *)NULL); + goto error; + } + } else if (piPtr->token != VALUE) { + Tcl_AppendResult(interp, "missing operand", (char *)NULL); + goto error; + } + } + if (!gotOp) { + result = NextToken(interp, piPtr, &value2); + if (result != TCL_OK) { + goto done; + } + } + /* + * Got the first operand. Now fetch (operator, operand) pairs. + */ + for (;;) { + operator = piPtr->token; + + value2.pv.next = value2.pv.buffer; + if ((operator < MULT) || (operator >= UNARY_MINUS)) { + if ((operator == END) || (operator == CLOSE_PAREN) || + (operator == COMMA)) { + result = TCL_OK; + goto done; + } else { + Tcl_AppendResult(interp, "bad operator", (char *)NULL); + goto error; + } + } + if (precTable[operator] <= prec) { + result = TCL_OK; + goto done; + } + result = NextValue(interp, piPtr, precTable[operator], &value2); + if (result != TCL_OK) { + goto done; + } + if ((piPtr->token < MULT) && (piPtr->token != VALUE) && + (piPtr->token != END) && (piPtr->token != CLOSE_PAREN) && + (piPtr->token != COMMA)) { + Tcl_AppendResult(interp, "unexpected token in expression", + (char *)NULL); + goto error; + } + /* + * At this point we have two vectors and an operator. + */ + + if (v2Ptr->length == 1) { + double *opnd; + double scalar; + + /* + * 2nd operand is a scalar. + */ + scalar = v2Ptr->valueArr[0]; + opnd = vPtr->valueArr; + switch (operator) { + case MULT: + for(i = 0; i < vPtr->length; i++) { + opnd[i] *= scalar; + } + break; + + case DIVIDE: + if (scalar == 0.0) { + Tcl_AppendResult(interp, "divide by zero", (char *)NULL); + goto error; + } + for(i = 0; i < vPtr->length; i++) { + opnd[i] /= scalar; + } + break; + + case PLUS: + for(i = 0; i < vPtr->length; i++) { + opnd[i] += scalar; + } + break; + + case MINUS: + for(i = 0; i < vPtr->length; i++) { + opnd[i] -= scalar; + } + break; + + case EXPONENT: + for(i = 0; i < vPtr->length; i++) { + opnd[i] = pow(opnd[i], scalar); + } + break; + + case MOD: + for(i = 0; i < vPtr->length; i++) { + opnd[i] = Fmod(opnd[i], scalar); + } + break; + + case LESS: + for(i = 0; i < vPtr->length; i++) { + opnd[i] = (double)(opnd[i] < scalar); + } + break; + + case GREATER: + for(i = 0; i < vPtr->length; i++) { + opnd[i] = (double)(opnd[i] > scalar); + } + break; + + case LEQ: + for(i = 0; i < vPtr->length; i++) { + opnd[i] = (double)(opnd[i] <= scalar); + } + break; + + case GEQ: + for(i = 0; i < vPtr->length; i++) { + opnd[i] = (double)(opnd[i] >= scalar); + } + break; + + case EQUAL: + for(i = 0; i < vPtr->length; i++) { + opnd[i] = (double)(opnd[i] == scalar); + } + break; + + case NEQ: + for(i = 0; i < vPtr->length; i++) { + opnd[i] = (double)(opnd[i] != scalar); + } + break; + + case AND: + for(i = 0; i < vPtr->length; i++) { + opnd[i] = (double)(opnd[i] && scalar); + } + break; + + case OR: + for(i = 0; i < vPtr->length; i++) { + opnd[i] = (double)(opnd[i] || scalar); + } + break; + + case LEFT_SHIFT: + { + int offset; + + offset = (int)scalar % vPtr->length; + if (offset > 0) { + double *hold; + int j; + + hold = Blt_AssertMalloc(sizeof(double) * offset); + for (i = 0; i < offset; i++) { + hold[i] = opnd[i]; + } + for (i = offset, j = 0; i < vPtr->length; i++, j++) { + opnd[j] = opnd[i]; + } + for (i = 0, j = vPtr->length - offset; + j < vPtr->length; i++, j++) { + opnd[j] = hold[i]; + } + Blt_Free(hold); + } + } + break; + + case RIGHT_SHIFT: + { + int offset; + + offset = (int)scalar % vPtr->length; + if (offset > 0) { + double *hold; + int j; + + hold = Blt_AssertMalloc(sizeof(double) * offset); + for (i = vPtr->length - offset, j = 0; + i < vPtr->length; i++, j++) { + hold[j] = opnd[i]; + } + for (i = vPtr->length - offset - 1, + j = vPtr->length - 1; i >= 0; i--, j--) { + opnd[j] = opnd[i]; + } + for (i = 0; i < offset; i++) { + opnd[i] = hold[i]; + } + Blt_Free(hold); + } + } + break; + + default: + Tcl_AppendResult(interp, "unknown operator in expression", + (char *)NULL); + goto error; + } + + } else if (vPtr->length == 1) { + double *opnd; + double scalar; + + /* + * 1st operand is a scalar. + */ + scalar = vPtr->valueArr[0]; + Blt_Vec_Duplicate(vPtr, v2Ptr); + opnd = vPtr->valueArr; + switch (operator) { + case MULT: + for(i = 0; i < vPtr->length; i++) { + opnd[i] *= scalar; + } + break; + + case PLUS: + for(i = 0; i < vPtr->length; i++) { + opnd[i] += scalar; + } + break; + + case DIVIDE: + for(i = 0; i < vPtr->length; i++) { + if (opnd[i] == 0.0) { + Tcl_AppendResult(interp, "divide by zero", + (char *)NULL); + goto error; + } + opnd[i] = (scalar / opnd[i]); + } + break; + + case MINUS: + for(i = 0; i < vPtr->length; i++) { + opnd[i] = scalar - opnd[i]; + } + break; + + case EXPONENT: + for(i = 0; i < vPtr->length; i++) { + opnd[i] = pow(scalar, opnd[i]); + } + break; + + case MOD: + for(i = 0; i < vPtr->length; i++) { + opnd[i] = Fmod(scalar, opnd[i]); + } + break; + + case LESS: + for(i = 0; i < vPtr->length; i++) { + opnd[i] = (double)(scalar < opnd[i]); + } + break; + + case GREATER: + for(i = 0; i < vPtr->length; i++) { + opnd[i] = (double)(scalar > opnd[i]); + } + break; + + case LEQ: + for(i = 0; i < vPtr->length; i++) { + opnd[i] = (double)(scalar >= opnd[i]); + } + break; + + case GEQ: + for(i = 0; i < vPtr->length; i++) { + opnd[i] = (double)(scalar <= opnd[i]); + } + break; + + case EQUAL: + for(i = 0; i < vPtr->length; i++) { + opnd[i] = (double)(opnd[i] == scalar); + } + break; + + case NEQ: + for(i = 0; i < vPtr->length; i++) { + opnd[i] = (double)(opnd[i] != scalar); + } + break; + + case AND: + for(i = 0; i < vPtr->length; i++) { + opnd[i] = (double)(opnd[i] && scalar); + } + break; + + case OR: + for(i = 0; i < vPtr->length; i++) { + opnd[i] = (double)(opnd[i] || scalar); + } + break; + + case LEFT_SHIFT: + case RIGHT_SHIFT: + Tcl_AppendResult(interp, "second shift operand must be scalar", + (char *)NULL); + goto error; + + default: + Tcl_AppendResult(interp, "unknown operator in expression", + (char *)NULL); + goto error; + } + } else { + double *opnd1, *opnd2; + /* + * Carry out the function of the specified operator. + */ + if (vPtr->length != v2Ptr->length) { + Tcl_AppendResult(interp, "vectors are different lengths", + (char *)NULL); + goto error; + } + opnd1 = vPtr->valueArr, opnd2 = v2Ptr->valueArr; + switch (operator) { + case MULT: + for (i = 0; i < vPtr->length; i++) { + opnd1[i] *= opnd2[i]; + } + break; + + case DIVIDE: + for (i = 0; i < vPtr->length; i++) { + if (opnd2[i] == 0.0) { + Tcl_AppendResult(interp, + "can't divide by 0.0 vector component", + (char *)NULL); + goto error; + } + opnd1[i] /= opnd2[i]; + } + break; + + case PLUS: + for (i = 0; i < vPtr->length; i++) { + opnd1[i] += opnd2[i]; + } + break; + + case MINUS: + for (i = 0; i < vPtr->length; i++) { + opnd1[i] -= opnd2[i]; + } + break; + + case MOD: + for (i = 0; i < vPtr->length; i++) { + opnd1[i] = Fmod(opnd1[i], opnd2[i]); + } + break; + + case EXPONENT: + for (i = 0; i < vPtr->length; i++) { + opnd1[i] = pow(opnd1[i], opnd2[i]); + } + break; + + case LESS: + for (i = 0; i < vPtr->length; i++) { + opnd1[i] = (double)(opnd1[i] < opnd2[i]); + } + break; + + case GREATER: + for (i = 0; i < vPtr->length; i++) { + opnd1[i] = (double)(opnd1[i] > opnd2[i]); + } + break; + + case LEQ: + for (i = 0; i < vPtr->length; i++) { + opnd1[i] = (double)(opnd1[i] <= opnd2[i]); + } + break; + + case GEQ: + for (i = 0; i < vPtr->length; i++) { + opnd1[i] = (double)(opnd1[i] >= opnd2[i]); + } + break; + + case EQUAL: + for (i = 0; i < vPtr->length; i++) { + opnd1[i] = (double)(opnd1[i] == opnd2[i]); + } + break; + + case NEQ: + for (i = 0; i < vPtr->length; i++) { + opnd1[i] = (double)(opnd1[i] != opnd2[i]); + } + break; + + case AND: + for (i = 0; i < vPtr->length; i++) { + opnd1[i] = (double)(opnd1[i] && opnd2[i]); + } + break; + + case OR: + for (i = 0; i < vPtr->length; i++) { + opnd1[i] = (double)(opnd1[i] || opnd2[i]); + } + break; + + case LEFT_SHIFT: + case RIGHT_SHIFT: + Tcl_AppendResult(interp, "second shift operand must be scalar", + (char *)NULL); + goto error; + + default: + Tcl_AppendResult(interp, "unknown operator in expression", + (char *)NULL); + goto error; + } + } + } + done: + if (value2.pv.buffer != value2.staticSpace) { + Blt_Free(value2.pv.buffer); + } + Blt_Vec_Free(v2Ptr); + return result; + + error: + if (value2.pv.buffer != value2.staticSpace) { + Blt_Free(value2.pv.buffer); + } + Blt_Vec_Free(v2Ptr); + return TCL_ERROR; +} + +/* + *--------------------------------------------------------------------------- + * + * EvaluateExpression -- + * + * This procedure provides top-level functionality shared by + * procedures like Tcl_ExprInt, Tcl_ExprDouble, etc. + * + * Results: + * The result is a standard TCL return value. If an error + * occurs then an error message is left in interp->result. + * The value of the expression is returned in *valuePtr, in + * whatever form it ends up in (could be string or integer + * or double). Caller may need to convert result. Caller + * is also responsible for freeing string memory in *valuePtr, + * if any was allocated. + * + * Side effects: + * None. + * + *--------------------------------------------------------------------------- + */ +static int +EvaluateExpression( + Tcl_Interp *interp, /* Context in which to evaluate the + * expression. */ + char *string, /* Expression to evaluate. */ + Value *valuePtr) /* Where to store result. Should + * not be initialized by caller. */ +{ + ParseInfo info; + int result; + Vector *vPtr; + double *vp, *vend; + + info.expr = info.nextPtr = string; + valuePtr->pv.buffer = valuePtr->pv.next = valuePtr->staticSpace; + valuePtr->pv.end = valuePtr->pv.buffer + STATIC_STRING_SPACE - 1; + valuePtr->pv.expandProc = Blt_ExpandParseValue; + valuePtr->pv.clientData = NULL; + + result = NextValue(interp, &info, -1, valuePtr); + if (result != TCL_OK) { + return result; + } + if (info.token != END) { + Tcl_AppendResult(interp, ": syntax error in expression \"", + string, "\"", (char *)NULL); + return TCL_ERROR; + } + vPtr = valuePtr->vPtr; + + /* Check for NaN's and overflows. */ + for (vp = vPtr->valueArr, vend = vp + vPtr->length; vp < vend; vp++) { + if (!FINITE(*vp)) { + /* + * IEEE floating-point error. + */ + MathError(interp, *vp); + return TCL_ERROR; + } + } + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * Math Functions -- + * + * This page contains the procedures that implement all of the + * built-in math functions for expressions. + * + * Results: + * Each procedure returns TCL_OK if it succeeds and places result + * information at *resultPtr. If it fails it returns TCL_ERROR + * and leaves an error message in interp->result. + * + * Side effects: + * None. + * + *--------------------------------------------------------------------------- + */ +static int +ComponentFunc( + ClientData clientData, /* Contains address of procedure that + * takes one double argument and + * returns a double result. */ + Tcl_Interp *interp, + Vector *vPtr) +{ + ComponentProc *procPtr = (ComponentProc *) clientData; + double *vp, *vend; + + errno = 0; + for(vp = vPtr->valueArr + vPtr->first, + vend = vPtr->valueArr + vPtr->last; vp <= vend; vp++) { + *vp = (*procPtr) (*vp); + if (errno != 0) { + MathError(interp, *vp); + return TCL_ERROR; + } + if (!FINITE(*vp)) { + /* + * IEEE floating-point error. + */ + MathError(interp, *vp); + return TCL_ERROR; + } + } + return TCL_OK; +} + +static int +ScalarFunc(ClientData clientData, Tcl_Interp *interp, Vector *vPtr) +{ + double value; + ScalarProc *procPtr = (ScalarProc *) clientData; + + errno = 0; + value = (*procPtr) (vPtr); + if (errno != 0) { + MathError(interp, value); + return TCL_ERROR; + } + if (Blt_Vec_ChangeLength(interp, vPtr, 1) != TCL_OK) { + return TCL_ERROR; + } + vPtr->valueArr[0] = value; + return TCL_OK; +} + +/*ARGSUSED*/ +static int +VectorFunc(ClientData clientData, Tcl_Interp *interp, Vector *vPtr) +{ + VectorProc *procPtr = (VectorProc *) clientData; + + return (*procPtr) (vPtr); +} + + +static MathFunction mathFunctions[] = +{ + {"abs", ComponentFunc, Fabs}, + {"acos", ComponentFunc, acos}, + {"asin", ComponentFunc, asin}, + {"atan", ComponentFunc, atan}, + {"adev", ScalarFunc, AvgDeviation}, + {"ceil", ComponentFunc, ceil}, + {"cos", ComponentFunc, cos}, + {"cosh", ComponentFunc, cosh}, + {"exp", ComponentFunc, exp}, + {"floor", ComponentFunc, floor}, + {"kurtosis",ScalarFunc, Kurtosis}, + {"length", ScalarFunc, Length}, + {"log", ComponentFunc, log}, + {"log10", ComponentFunc, log10}, + {"max", ScalarFunc, Blt_VecMax}, + {"mean", ScalarFunc, Mean}, + {"median", ScalarFunc, Median}, + {"min", ScalarFunc, Blt_VecMin}, + {"norm", VectorFunc, Norm}, + {"nz", ScalarFunc, Nonzeros}, + {"q1", ScalarFunc, Q1}, + {"q3", ScalarFunc, Q3}, + {"prod", ScalarFunc, Product}, + {"random", ComponentFunc, drand48}, + {"round", ComponentFunc, Round}, + {"sdev", ScalarFunc, StdDeviation}, + {"sin", ComponentFunc, sin}, + {"sinh", ComponentFunc, sinh}, + {"skew", ScalarFunc, Skew}, + {"sort", VectorFunc, Sort}, + {"sqrt", ComponentFunc, sqrt}, + {"sum", ScalarFunc, Sum}, + {"tan", ComponentFunc, tan}, + {"tanh", ComponentFunc, tanh}, + {"var", ScalarFunc, Variance}, + {(char *)NULL,}, +}; + +void +Blt_Vec_InstallMathFunctions(Blt_HashTable *tablePtr) +{ + MathFunction *mathPtr; + + for (mathPtr = mathFunctions; mathPtr->name != NULL; mathPtr++) { + Blt_HashEntry *hPtr; + int isNew; + + hPtr = Blt_CreateHashEntry(tablePtr, mathPtr->name, &isNew); + Blt_SetHashValue(hPtr, (ClientData)mathPtr); + } +} + +void +Blt_Vec_UninstallMathFunctions(Blt_HashTable *tablePtr) +{ + Blt_HashEntry *hPtr; + Blt_HashSearch cursor; + + for (hPtr = Blt_FirstHashEntry(tablePtr, &cursor); hPtr != NULL; + hPtr = Blt_NextHashEntry(&cursor)) { + MathFunction *mathPtr; + + mathPtr = Blt_GetHashValue(hPtr); + if (mathPtr->name == NULL) { + Blt_Free(mathPtr); + } + } +} + + +static void +InstallIndexProc( + Blt_HashTable *tablePtr, + const char *string, + Blt_VectorIndexProc *procPtr) /* Pointer to function to be called + * when the vector finds the named index. + * If NULL, this indicates to remove + * the index from the table. + */ +{ + Blt_HashEntry *hPtr; + int dummy; + + hPtr = Blt_CreateHashEntry(tablePtr, string, &dummy); + if (procPtr == NULL) { + Blt_DeleteHashEntry(tablePtr, hPtr); + } else { + Blt_SetHashValue(hPtr, (ClientData)procPtr); + } +} + +void +Blt_Vec_InstallSpecialIndices(Blt_HashTable *tablePtr) +{ + InstallIndexProc(tablePtr, "min", Blt_VecMin); + InstallIndexProc(tablePtr, "max", Blt_VecMax); + InstallIndexProc(tablePtr, "mean", Mean); + InstallIndexProc(tablePtr, "sum", Sum); + InstallIndexProc(tablePtr, "prod", Product); +} + + +/* + *--------------------------------------------------------------------------- + * + * Blt_ExprVector -- + * + * Evaluates an vector expression and returns its value(s). + * + * Results: + * Each of the procedures below returns a standard TCL result. + * If an error occurs then an error message is left in + * interp->result. Otherwise the value of the expression, + * in the appropriate form, is stored at *resultPtr. If + * the expression had a result that was incompatible with the + * desired form then an error is returned. + * + * Side effects: + * None. + * + *--------------------------------------------------------------------------- + */ +int +Blt_ExprVector( + Tcl_Interp *interp, /* Context in which to evaluate the + * expression. */ + char *string, /* Expression to evaluate. */ + Blt_Vector *vector) /* Where to store result. */ +{ + VectorInterpData *dataPtr; /* Interpreter-specific data. */ + Vector *vPtr = (Vector *)vector; + Value value; + + dataPtr = (vector != NULL) + ? vPtr->dataPtr : Blt_Vec_GetInterpData(interp); + value.vPtr = Blt_Vec_New(dataPtr); + if (EvaluateExpression(interp, string, &value) != TCL_OK) { + Blt_Vec_Free(value.vPtr); + return TCL_ERROR; + } + if (vPtr != NULL) { + Blt_Vec_Duplicate(vPtr, value.vPtr); + } else { + Tcl_Obj *listObjPtr; + double *vp, *vend; + + /* No result vector. Put values in interp->result. */ + listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **) NULL); + for (vp = value.vPtr->valueArr, vend = vp + value.vPtr->length; + vp < vend; vp++) { + Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewDoubleObj(*vp)); + } + Tcl_SetObjResult(interp, listObjPtr); + } + Blt_Vec_Free(value.vPtr); + return TCL_OK; +} diff --git a/src/bltVector.C b/src/bltVector.C new file mode 100644 index 0000000..e332b9d --- /dev/null +++ b/src/bltVector.C @@ -0,0 +1,2808 @@ + +/* + * bltVector.c -- + * + * This module implements vector data objects. + * + * Copyright 1995-2004 George A Howlett. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* + * TODO: + * o Add H. Kirsch's vector binary read operation + * x binread file0 + * x binread -file file0 + * + * o Add ASCII/binary file reader + * x read fileName + * + * o Allow Tcl-based client notifications. + * vector x + * x notify call Display + * x notify delete Display + * x notify reorder #1 #2 + */ + +#include "bltVecInt.h" +#include "bltOp.h" +#include "bltNsUtil.h" +#include "bltSwitch.h" +#include <bltMath.h> + +#ifdef TIME_WITH_SYS_TIME +#include <sys/time.h> +#include <time.h> +#else +#ifdef HAVE_SYS_TIME_H +#include <sys/time.h> +#else +#include <time.h> +#endif /* HAVE_SYS_TIME_H */ +#endif /* TIME_WITH_SYS_TIME */ + +#ifndef TCL_NAMESPACE_ONLY +#define TCL_NAMESPACE_ONLY TCL_GLOBAL_ONLY +#endif + +#define DEF_ARRAY_SIZE 64 +#define TRACE_ALL (TCL_TRACE_WRITES | TCL_TRACE_READS | TCL_TRACE_UNSETS) + + +#define VECTOR_CHAR(c) ((isalnum(UCHAR(c))) || \ + (c == '_') || (c == ':') || (c == '@') || (c == '.')) + + +/* + * VectorClient -- + * + * A vector can be shared by several clients. Each client allocates this + * structure that acts as its key for using the vector. Clients can also + * designate a callback routine that is executed whenever the vector is + * updated or destroyed. + * + */ +typedef struct { + unsigned int magic; /* Magic value designating whether this really + * is a vector token or not */ + + Vector *serverPtr; /* Pointer to the master record of the vector. + * If NULL, indicates that the vector has been + * destroyed but as of yet, this client hasn't + * recognized it. */ + + Blt_VectorChangedProc *proc;/* Routine to call when the contents of the + * vector change or the vector is deleted. */ + + ClientData clientData; /* Data passed whenever the vector change + * procedure is called. */ + + Blt_ChainLink link; /* Used to quickly remove this entry from its + * server's client chain. */ +} VectorClient; + +static Tcl_CmdDeleteProc VectorInstDeleteProc; +static Tcl_ObjCmdProc VectorCmd; +static Tcl_InterpDeleteProc VectorInterpDeleteProc; + +typedef struct { + char *varName; /* Requested variable name. */ + char *cmdName; /* Requested command name. */ + int flush; /* Flush */ + int watchUnset; /* Watch when variable is unset. */ +} CreateSwitches; + +static Blt_SwitchSpec createSwitches[] = +{ + {BLT_SWITCH_STRING, "-variable", "varName", + Blt_Offset(CreateSwitches, varName), BLT_SWITCH_NULL_OK}, + {BLT_SWITCH_STRING, "-command", "command", + Blt_Offset(CreateSwitches, cmdName), BLT_SWITCH_NULL_OK}, + {BLT_SWITCH_BOOLEAN, "-watchunset", "bool", + Blt_Offset(CreateSwitches, watchUnset), 0}, + {BLT_SWITCH_BOOLEAN, "-flush", "bool", + Blt_Offset(CreateSwitches, flush), 0}, + {BLT_SWITCH_END} +}; + +typedef int (VectorCmdProc)(Vector *vecObjPtr, Tcl_Interp *interp, + int objc, Tcl_Obj *const *objv); + +static Vector * +FindVectorInNamespace( + VectorInterpData *dataPtr, /* Interpreter-specific data. */ + Blt_ObjectName *objNamePtr) +{ + Tcl_DString dString; + const char *name; + Blt_HashEntry *hPtr; + + name = Blt_MakeQualifiedName(objNamePtr, &dString); + hPtr = Blt_FindHashEntry(&dataPtr->vectorTable, name); + Tcl_DStringFree(&dString); + if (hPtr != NULL) { + return Blt_GetHashValue(hPtr); + } + return NULL; +} + +/* + *--------------------------------------------------------------------------- + * + * GetVectorObject -- + * + * Searches for the vector associated with the name given. Allow for a + * range specification. + * + * Results: + * Returns a pointer to the vector if found, otherwise NULL. + * + *--------------------------------------------------------------------------- + */ +static Vector * +GetVectorObject( + VectorInterpData *dataPtr, /* Interpreter-specific data. */ + const char *name, + int flags) +{ + Blt_ObjectName objName; + Vector *vPtr; + Tcl_Interp *interp; + + interp = dataPtr->interp; + if (!Blt_ParseObjectName(interp, name, &objName, + BLT_NO_ERROR_MSG | BLT_NO_DEFAULT_NS)) { + return NULL; /* Can't find namespace. */ + } + vPtr = NULL; + if (objName.nsPtr != NULL) { + vPtr = FindVectorInNamespace(dataPtr, &objName); + } else { + if (flags & NS_SEARCH_CURRENT) { + objName.nsPtr = Tcl_GetCurrentNamespace(interp); + vPtr = FindVectorInNamespace(dataPtr, &objName); + } + if ((vPtr == NULL) && (flags & NS_SEARCH_GLOBAL)) { + objName.nsPtr = Tcl_GetGlobalNamespace(interp); + vPtr = FindVectorInNamespace(dataPtr, &objName); + } + } + return vPtr; +} + +void +Blt_Vec_UpdateRange(Vector *vPtr) +{ + double min, max; + double *vp, *vend; + + vp = vPtr->valueArr + vPtr->first; + vend = vPtr->valueArr + vPtr->last; + min = max = *vp++; + for (/* empty */; vp <= vend; vp++) { + if (min > *vp) { + min = *vp; + } else if (max < *vp) { + max = *vp; + } + } + vPtr->min = min; + vPtr->max = max; + vPtr->notifyFlags &= ~UPDATE_RANGE; +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_Vec_GetIndex -- + * + * Converts the string representing an index in the vector, to its + * numeric value. A valid index may be an numeric string of the string + * "end" (indicating the last element in the string). + * + * Results: + * A standard TCL result. If the string is a valid index, TCL_OK is + * returned. Otherwise TCL_ERROR is returned and interp->result will + * contain an error message. + * + *--------------------------------------------------------------------------- + */ +int +Blt_Vec_GetIndex( + Tcl_Interp *interp, + Vector *vPtr, + const char *string, + int *indexPtr, + int flags, + Blt_VectorIndexProc **procPtrPtr) +{ + char c; + int value; + + c = string[0]; + + /* Treat the index "end" like a numeric index. */ + + if ((c == 'e') && (strcmp(string, "end") == 0)) { + if (vPtr->length < 1) { + if (interp != NULL) { + Tcl_AppendResult(interp, "bad index \"end\": vector is empty", + (char *)NULL); + } + return TCL_ERROR; + } + *indexPtr = vPtr->length - 1; + return TCL_OK; + } else if ((c == '+') && (strcmp(string, "++end") == 0)) { + *indexPtr = vPtr->length; + return TCL_OK; + } + if (procPtrPtr != NULL) { + Blt_HashEntry *hPtr; + + hPtr = Blt_FindHashEntry(&vPtr->dataPtr->indexProcTable, string); + if (hPtr != NULL) { + *indexPtr = SPECIAL_INDEX; + *procPtrPtr = Blt_GetHashValue(hPtr); + return TCL_OK; + } + } + if (Tcl_GetInt(interp, (char *)string, &value) != TCL_OK) { + long int lvalue; + /* + * Unlike Tcl_GetInt, Tcl_ExprLong needs a valid interpreter, but the + * interp passed in may be NULL. So we have to use vPtr->interp and + * then reset the result. + */ + if (Tcl_ExprLong(vPtr->interp, (char *)string, &lvalue) != TCL_OK) { + Tcl_ResetResult(vPtr->interp); + if (interp != NULL) { + Tcl_AppendResult(interp, "bad index \"", string, "\"", + (char *)NULL); + } + return TCL_ERROR; + } + value = (int)lvalue; + } + /* + * Correct the index by the current value of the offset. This makes all + * the numeric indices non-negative, which is how we distinguish the + * special non-numeric indices. + */ + value -= vPtr->offset; + + if ((value < 0) || ((flags & INDEX_CHECK) && (value >= vPtr->length))) { + if (interp != NULL) { + Tcl_AppendResult(interp, "index \"", string, "\" is out of range", + (char *)NULL); + } + return TCL_ERROR; + } + *indexPtr = (int)value; + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_Vec_GetIndexRange -- + * + * Converts the string representing an index in the vector, to its + * numeric value. A valid index may be an numeric string of the string + * "end" (indicating the last element in the string). + * + * Results: + * A standard TCL result. If the string is a valid index, TCL_OK is + * returned. Otherwise TCL_ERROR is returned and interp->result will + * contain an error message. + * + *--------------------------------------------------------------------------- + */ +int +Blt_Vec_GetIndexRange( + Tcl_Interp *interp, + Vector *vPtr, + const char *string, + int flags, + Blt_VectorIndexProc **procPtrPtr) +{ + int ielem; + char *colon; + + colon = NULL; + if (flags & INDEX_COLON) { + colon = strchr(string, ':'); + } + if (colon != NULL) { + if (string == colon) { + vPtr->first = 0; /* Default to the first index */ + } else { + int result; + + *colon = '\0'; + result = Blt_Vec_GetIndex(interp, vPtr, string, &ielem, flags, + (Blt_VectorIndexProc **) NULL); + *colon = ':'; + if (result != TCL_OK) { + return TCL_ERROR; + } + vPtr->first = ielem; + } + if (*(colon + 1) == '\0') { + /* Default to the last index */ + vPtr->last = (vPtr->length > 0) ? vPtr->length - 1 : 0; + } else { + if (Blt_Vec_GetIndex(interp, vPtr, colon + 1, &ielem, flags, + (Blt_VectorIndexProc **) NULL) != TCL_OK) { + return TCL_ERROR; + } + vPtr->last = ielem; + } + if (vPtr->first > vPtr->last) { + if (interp != NULL) { + Tcl_AppendResult(interp, "bad range \"", string, + "\" (first > last)", (char *)NULL); + } + return TCL_ERROR; + } + } else { + if (Blt_Vec_GetIndex(interp, vPtr, string, &ielem, flags, + procPtrPtr) != TCL_OK) { + return TCL_ERROR; + } + vPtr->last = vPtr->first = ielem; + } + return TCL_OK; +} + +Vector * +Blt_Vec_ParseElement( + Tcl_Interp *interp, + VectorInterpData *dataPtr, /* Interpreter-specific data. */ + const char *start, + const char **endPtr, + int flags) +{ + char *p; + char saved; + Vector *vPtr; + + p = (char *)start; + /* Find the end of the vector name */ + while (VECTOR_CHAR(*p)) { + p++; + } + saved = *p; + *p = '\0'; + + vPtr = GetVectorObject(dataPtr, start, flags); + if (vPtr == NULL) { + if (interp != NULL) { + Tcl_AppendResult(interp, "can't find vector \"", start, "\"", + (char *)NULL); + } + *p = saved; + return NULL; + } + *p = saved; + vPtr->first = 0; + vPtr->last = vPtr->length - 1; + if (*p == '(') { + int count, result; + + start = p + 1; + p++; + + /* Find the matching right parenthesis */ + count = 1; + while (*p != '\0') { + if (*p == ')') { + count--; + if (count == 0) { + break; + } + } else if (*p == '(') { + count++; + } + p++; + } + if (count > 0) { + if (interp != NULL) { + Tcl_AppendResult(interp, "unbalanced parentheses \"", start, + "\"", (char *)NULL); + } + return NULL; + } + *p = '\0'; + result = Blt_Vec_GetIndexRange(interp, vPtr, start, + (INDEX_COLON | INDEX_CHECK), (Blt_VectorIndexProc **) NULL); + *p = ')'; + if (result != TCL_OK) { + return NULL; + } + p++; + } + if (endPtr != NULL) { + *endPtr = p; + } + return vPtr; +} + + +/* + *--------------------------------------------------------------------------- + * + * Blt_Vec_NotifyClients -- + * + * Notifies each client of the vector that the vector has changed + * (updated or destroyed) by calling the provided function back. The + * function pointer may be NULL, in that case the client is not notified. + * + * Results: + * None. + * + * Side effects: + * The results depend upon what actions the client callbacks + * take. + * + *--------------------------------------------------------------------------- + */ +void +Blt_Vec_NotifyClients(ClientData clientData) +{ + Vector *vPtr = clientData; + Blt_ChainLink link, next; + Blt_VectorNotify notify; + + notify = (vPtr->notifyFlags & NOTIFY_DESTROYED) + ? BLT_VECTOR_NOTIFY_DESTROY : BLT_VECTOR_NOTIFY_UPDATE; + vPtr->notifyFlags &= ~(NOTIFY_UPDATED | NOTIFY_DESTROYED | NOTIFY_PENDING); + for (link = Blt_Chain_FirstLink(vPtr->chain); link != NULL; link = next) { + VectorClient *clientPtr; + + next = Blt_Chain_NextLink(link); + clientPtr = Blt_Chain_GetValue(link); + if ((clientPtr->proc != NULL) && (clientPtr->serverPtr != NULL)) { + (*clientPtr->proc) (vPtr->interp, clientPtr->clientData, notify); + } + } + /* + * Some clients may not handle the "destroy" callback properly (they + * should call Blt_FreeVectorId to release the client identifier), so mark + * any remaining clients to indicate that vector's server has gone away. + */ + if (notify == BLT_VECTOR_NOTIFY_DESTROY) { + for (link = Blt_Chain_FirstLink(vPtr->chain); link != NULL; + link = Blt_Chain_NextLink(link)) { + VectorClient *clientPtr; + + clientPtr = Blt_Chain_GetValue(link); + clientPtr->serverPtr = NULL; + } + } +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_Vec_UpdateClients -- + * + * Notifies each client of the vector that the vector has changed + * (updated or destroyed) by calling the provided function back. + * + * Results: + * None. + * + * Side effects: + * The individual client callbacks are eventually invoked. + * + *--------------------------------------------------------------------------- + */ +void +Blt_Vec_UpdateClients(Vector *vPtr) +{ + vPtr->dirty++; + vPtr->max = vPtr->min = Blt_NaN(); + if (vPtr->notifyFlags & NOTIFY_NEVER) { + return; + } + vPtr->notifyFlags |= NOTIFY_UPDATED; + if (vPtr->notifyFlags & NOTIFY_ALWAYS) { + Blt_Vec_NotifyClients(vPtr); + return; + } + if (!(vPtr->notifyFlags & NOTIFY_PENDING)) { + vPtr->notifyFlags |= NOTIFY_PENDING; + Tcl_DoWhenIdle(Blt_Vec_NotifyClients, vPtr); + } +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_Vec_FlushCache -- + * + * Unsets all the elements of the TCL array variable associated with the + * vector, freeing memory associated with the variable. This includes + * both the hash table and the hash keys. The down side is that this + * effectively flushes the caching of vector elements in the array. This + * means that the subsequent reads of the array will require a decimal to + * string conversion. + * + * This is needed when the vector changes its values, making the array + * variable out-of-sync. + * + * Results: + * None. + * + * Side effects: + * All elements of array variable (except one) are unset, freeing + * the memory associated with the variable. + * + *--------------------------------------------------------------------------- + */ +void +Blt_Vec_FlushCache(Vector *vPtr) +{ + Tcl_Interp *interp = vPtr->interp; + + if (vPtr->arrayName == NULL) { + return; /* Doesn't use the variable API */ + } + /* Turn off the trace temporarily so that we can unset all the + * elements in the array. */ + + Tcl_UntraceVar2(interp, vPtr->arrayName, (char *)NULL, + TRACE_ALL | vPtr->varFlags, Blt_Vec_VarTrace, vPtr); + + /* Clear all the element entries from the entire array */ + Tcl_UnsetVar2(interp, vPtr->arrayName, (char *)NULL, vPtr->varFlags); + + /* Restore the "end" index by default and the trace on the entire array */ + Tcl_SetVar2(interp, vPtr->arrayName, "end", "", vPtr->varFlags); + Tcl_TraceVar2(interp, vPtr->arrayName, (char *)NULL, + TRACE_ALL | vPtr->varFlags, Blt_Vec_VarTrace, vPtr); +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_Vec_LookupName -- + * + * Searches for the vector associated with the name given. Allow for a + * range specification. + * + * Results: + * Returns a pointer to the vector if found, otherwise NULL. If the name + * is not associated with a vector and the TCL_LEAVE_ERR_MSG flag is set, + * and interp->result will contain an error message. + * + *--------------------------------------------------------------------------- + */ +int +Blt_Vec_LookupName( + VectorInterpData *dataPtr, /* Interpreter-specific data. */ + const char *vecName, + Vector **vPtrPtr) +{ + Vector *vPtr; + const char *endPtr; + + vPtr = Blt_Vec_ParseElement(dataPtr->interp, dataPtr, vecName, &endPtr, + NS_SEARCH_BOTH); + if (vPtr == NULL) { + return TCL_ERROR; + } + if (*endPtr != '\0') { + Tcl_AppendResult(dataPtr->interp, + "extra characters after vector name", (char *)NULL); + return TCL_ERROR; + } + *vPtrPtr = vPtr; + return TCL_OK; +} + +double +Blt_Vec_Min(Vector *vecObjPtr) +{ + double *vp, *vend; + double min; + + vp = vecObjPtr->valueArr + vecObjPtr->first; + vend = vecObjPtr->valueArr + vecObjPtr->last; + min = *vp++; + for (/* empty */; vp <= vend; vp++) { + if (min > *vp) { + min = *vp; + } + } + vecObjPtr->min = min; + return vecObjPtr->min; +} + +double +Blt_Vec_Max(Vector *vecObjPtr) +{ + double max; + double *vp, *vend; + + max = Blt_NaN(); + vp = vecObjPtr->valueArr + vecObjPtr->first; + vend = vecObjPtr->valueArr + vecObjPtr->last; + max = *vp++; + for (/* empty */; vp <= vend; vp++) { + if (max < *vp) { + max = *vp; + } + } + vecObjPtr->max = max; + return vecObjPtr->max; +} + +/* + *--------------------------------------------------------------------------- + * + * DeleteCommand -- + * + * Deletes the TCL command associated with the vector, without triggering + * a callback to "VectorInstDeleteProc". + * + * Results: + * None. + * + *--------------------------------------------------------------------------- + */ +static void +DeleteCommand(Vector *vPtr) /* Vector associated with the TCL command. */ +{ + Tcl_Interp *interp = vPtr->interp; + char *qualName; /* Name of TCL command. */ + Tcl_CmdInfo cmdInfo; + Tcl_DString dString; + Blt_ObjectName objName; + + Tcl_DStringInit(&dString); + objName.name = Tcl_GetCommandName(interp, vPtr->cmdToken); + objName.nsPtr = Blt_GetCommandNamespace(vPtr->cmdToken); + qualName = Blt_MakeQualifiedName(&objName, &dString); + if (Tcl_GetCommandInfo(interp, qualName, &cmdInfo)) { + /* Disable the callback before deleting the TCL command.*/ + cmdInfo.deleteProc = NULL; + Tcl_SetCommandInfo(interp, qualName, &cmdInfo); + Tcl_DeleteCommandFromToken(interp, vPtr->cmdToken); + } + Tcl_DStringFree(&dString); + vPtr->cmdToken = 0; +} + +/* + *--------------------------------------------------------------------------- + * + * UnmapVariable -- + * + * Destroys the trace on the current TCL variable designated to access + * the vector. + * + * Results: + * None. + * + *--------------------------------------------------------------------------- + */ +static void +UnmapVariable(Vector *vPtr) +{ + Tcl_Interp *interp = vPtr->interp; + + /* Unset the entire array */ + Tcl_UntraceVar2(interp, vPtr->arrayName, (char *)NULL, + (TRACE_ALL | vPtr->varFlags), Blt_Vec_VarTrace, vPtr); + Tcl_UnsetVar2(interp, vPtr->arrayName, (char *)NULL, vPtr->varFlags); + + if (vPtr->arrayName != NULL) { + Blt_Free(vPtr->arrayName); + vPtr->arrayName = NULL; + } +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_Vec_MapVariable -- + * + * Sets up traces on a TCL variable to access the vector. + * + * If another variable is already mapped, it's first untraced and + * removed. Don't do anything else for variables named "" (even though + * Tcl allows this pathology). Saves the name of the new array variable. + * + * Results: + * A standard TCL result. If an error occurs setting the variable + * TCL_ERROR is returned and an error message is left in the interpreter. + * + * Side effects: + * Traces are set for the new variable. The new variable name is saved in + * a malloc'ed string in vPtr->arrayName. If this variable is non-NULL, + * it indicates that a TCL variable has been mapped to this vector. + * + *--------------------------------------------------------------------------- + */ +int +Blt_Vec_MapVariable( + Tcl_Interp *interp, + Vector *vPtr, + const char *path) +{ + Blt_ObjectName objName; + char *newPath; + const char *result; + Tcl_DString dString; + + if (vPtr->arrayName != NULL) { + UnmapVariable(vPtr); + } + if ((path == NULL) || (path[0] == '\0')) { + return TCL_OK; /* If the variable pathname is the empty + * string, simply return after removing any + * existing variable. */ + } + /* Get the variable name (without the namespace qualifier). */ + if (!Blt_ParseObjectName(interp, path, &objName, BLT_NO_DEFAULT_NS)) { + return TCL_ERROR; + } + if (objName.nsPtr == NULL) { + /* + * If there was no namespace qualifier, try harder to see if the + * variable is non-local. + */ + objName.nsPtr = Blt_GetVariableNamespace(interp, objName.name); + } + Tcl_DStringInit(&dString); + vPtr->varFlags = 0; + if (objName.nsPtr != NULL) { /* Global or namespace variable. */ + newPath = Blt_MakeQualifiedName(&objName, &dString); + vPtr->varFlags |= (TCL_NAMESPACE_ONLY | TCL_GLOBAL_ONLY); + } else { /* Local variable. */ + newPath = (char *)objName.name; + } + + /* + * To play it safe, delete the variable first. This has the benefical + * side-effect of unmapping the variable from another vector that may be + * currently associated with it. + */ + Tcl_UnsetVar2(interp, newPath, (char *)NULL, 0); + + /* + * Set the index "end" in the array. This will create the variable + * immediately so that we can check its namespace context. + */ + result = Tcl_SetVar2(interp, newPath, "end", "", TCL_LEAVE_ERR_MSG); + if (result == NULL) { + Tcl_DStringFree(&dString); + return TCL_ERROR; + } + /* Create a full-array trace on reads, writes, and unsets. */ + Tcl_TraceVar2(interp, newPath, (char *)NULL, TRACE_ALL, Blt_Vec_VarTrace, + vPtr); + vPtr->arrayName = Blt_AssertStrdup(newPath); + Tcl_DStringFree(&dString); + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_Vec_SetSize -- + * + * Resizes the vector to the designated new size. + * + * If the new size is the same as the old, simply return. Otherwise + * we're copying the data from one memory location to another. + * + * If the storage changed memory locations, free up the old location if + * it was dynamically allocated. + * + * Results: + * A standard TCL result. If the reallocation is successful, + * TCL_OK is returned, otherwise TCL_ERROR. + * + * Side effects: + * Memory for the array is reallocated. + * + *--------------------------------------------------------------------------- + */ +int +Blt_Vec_SetSize( + Tcl_Interp *interp, + Vector *vPtr, + int newSize) /* Size of array in elements */ +{ + if (newSize <= 0) { + newSize = DEF_ARRAY_SIZE; + } + if (newSize == vPtr->size) { + /* Same size, use the current array. */ + return TCL_OK; + } + if (vPtr->freeProc == TCL_DYNAMIC) { + double *newArr; + + /* Old memory was dynamically allocated, so use realloc. */ + newArr = Blt_Realloc(vPtr->valueArr, newSize * sizeof(double)); + if (newArr == NULL) { + if (interp != NULL) { + Tcl_AppendResult(interp, "can't reallocate ", + Blt_Itoa(newSize), " elements for vector \"", + vPtr->name, "\"", (char *)NULL); + } + return TCL_ERROR; + } + vPtr->size = newSize; + vPtr->valueArr = newArr; + return TCL_OK; + } + + { + double *newArr; + + /* Old memory was created specially (static or special allocator). + * Replace with dynamically allocated memory (malloc-ed). */ + + newArr = Blt_Calloc(newSize, sizeof(double)); + if (newArr == NULL) { + if (interp != NULL) { + Tcl_AppendResult(interp, "can't allocate ", + Blt_Itoa(newSize), " elements for vector \"", + vPtr->name, "\"", (char *)NULL); + } + return TCL_ERROR; + } + { + int used, wanted; + + /* Copy the contents of the old memory into the new. */ + used = vPtr->length; + wanted = newSize; + + if (used > wanted) { + used = wanted; + } + /* Copy any previous data */ + if (used > 0) { + memcpy(newArr, vPtr->valueArr, used * sizeof(double)); + } + } + + assert(vPtr->valueArr != NULL); + + /* + * We're not using the old storage anymore, so free it if it's not + * TCL_STATIC. It's static because the user previously reset the + * vector with a statically allocated array (setting freeProc to + * TCL_STATIC). + */ + if (vPtr->freeProc != TCL_STATIC) { + if (vPtr->freeProc == TCL_DYNAMIC) { + Blt_Free(vPtr->valueArr); + } else { + (*vPtr->freeProc) ((char *)vPtr->valueArr); + } + } + vPtr->freeProc = TCL_DYNAMIC; /* Set the type of the new storage */ + vPtr->valueArr = newArr; + vPtr->size = newSize; + } + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_Vec_SetSize -- + * + * Set the length (the number of elements currently in use) of the + * vector. If the new length is greater than the size (total number of + * slots), then the vector is grown. + * + * Results: + * A standard TCL result. If the reallocation is successful, TCL_OK is + * returned, otherwise TCL_ERROR. + * + * Side effects: + * Memory for the array is possibly reallocated. + * + *--------------------------------------------------------------------------- + */ +int +Blt_Vec_SetLength( + Tcl_Interp *interp, + Vector *vPtr, + int newLength) /* Size of array in elements */ +{ + if (vPtr->size < newLength) { + if (Blt_Vec_SetSize(interp, vPtr, newLength) != TCL_OK) { + return TCL_ERROR; + } + } + vPtr->length = newLength; + vPtr->first = 0; + vPtr->last = newLength - 1; + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_Vec_ChangeLength -- + * + * Resizes the vector to the new size. + * + * The new size of the vector is computed by doubling the size of the + * vector until it fits the number of slots needed (designated by + * *length*). + * + * If the new size is the same as the old, simply adjust the length of + * the vector. Otherwise we're copying the data from one memory location + * to another. The trailing elements of the vector need to be reset to + * zero. + * + * If the storage changed memory locations, free up the old location if + * it was dynamically allocated. + * + * Results: + * A standard TCL result. If the reallocation is successful, + * TCL_OK is returned, otherwise TCL_ERROR. + * + * Side effects: + * Memory for the array is reallocated. + * + *--------------------------------------------------------------------------- + */ + +int +Blt_Vec_ChangeLength( + Tcl_Interp *interp, + Vector *vPtr, + int newLength) +{ + if (newLength < 0) { + newLength = 0; + } + if (newLength > vPtr->size) { + int newSize; /* Size of array in elements */ + + /* Compute the new size of the array. It's a multiple of + * DEF_ARRAY_SIZE. */ + newSize = DEF_ARRAY_SIZE; + while (newSize < newLength) { + newSize += newSize; + } + if (newSize != vPtr->size) { + if (Blt_Vec_SetSize(interp, vPtr, newSize) != TCL_OK) { + return TCL_ERROR; + } + } + } + vPtr->length = newLength; + vPtr->first = 0; + vPtr->last = newLength - 1; + return TCL_OK; + +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_Vec_Reset -- + * + * Resets the vector data. This is called by a client to indicate that + * the vector data has changed. The vector does not need to point to + * different memory. Any clients of the vector will be notified of the + * change. + * + * Results: + * A standard TCL result. If the new array size is invalid, TCL_ERROR is + * returned. Otherwise TCL_OK is returned and the new vector data is + * recorded. + * + * Side Effects: + * Any client designated callbacks will be posted. Memory may be changed + * for the vector array. + * + *--------------------------------------------------------------------------- + */ +int +Blt_Vec_Reset( + Vector *vPtr, + double *valueArr, /* Array containing the elements of the + * vector. If NULL, indicates to reset the + * vector size to the default. */ + int length, /* The number of elements that the vector + * currently holds. */ + int size, /* The maximum number of elements that the + * array can hold. */ + Tcl_FreeProc *freeProc) /* Address of memory deallocation routine + * for the array of values. Can also be + * TCL_STATIC, TCL_DYNAMIC, or TCL_VOLATILE. */ +{ + if (vPtr->valueArr != valueArr) { /* New array of values resides + * in different memory than + * the current vector. */ + if ((valueArr == NULL) || (size == 0)) { + /* Empty array. Set up default values */ + valueArr = Blt_Malloc(sizeof(double) * DEF_ARRAY_SIZE); + size = DEF_ARRAY_SIZE; + if (valueArr == NULL) { + Tcl_AppendResult(vPtr->interp, "can't allocate ", + Blt_Itoa(size), " elements for vector \"", + vPtr->name, "\"", (char *)NULL); + return TCL_ERROR; + } + freeProc = TCL_DYNAMIC; + length = 0; + } else if (freeProc == TCL_VOLATILE) { + double *newArr; + /* Data is volatile. Make a copy of the value array. */ + newArr = Blt_Malloc(size * sizeof(double)); + if (newArr == NULL) { + Tcl_AppendResult(vPtr->interp, "can't allocate ", + Blt_Itoa(size), " elements for vector \"", + vPtr->name, "\"", (char *)NULL); + return TCL_ERROR; + } + memcpy((char *)newArr, (char *)valueArr, + sizeof(double) * length); + valueArr = newArr; + freeProc = TCL_DYNAMIC; + } + + if (vPtr->freeProc != TCL_STATIC) { + /* Old data was dynamically allocated. Free it before attaching + * new data. */ + if (vPtr->freeProc == TCL_DYNAMIC) { + Blt_Free(vPtr->valueArr); + } else { + (*freeProc) ((char *)vPtr->valueArr); + } + } + vPtr->freeProc = freeProc; + vPtr->valueArr = valueArr; + vPtr->size = size; + } + + vPtr->length = length; + if (vPtr->flush) { + Blt_Vec_FlushCache(vPtr); + } + Blt_Vec_UpdateClients(vPtr); + return TCL_OK; +} + +Vector * +Blt_Vec_New(VectorInterpData *dataPtr) /* Interpreter-specific data. */ +{ + Vector *vPtr; + + vPtr = Blt_AssertCalloc(1, sizeof(Vector)); + vPtr->valueArr = Blt_Malloc(sizeof(double) * DEF_ARRAY_SIZE); + if (vPtr->valueArr == NULL) { + Blt_Free(vPtr); + return NULL; + } + vPtr->size = DEF_ARRAY_SIZE; + vPtr->freeProc = TCL_DYNAMIC; + vPtr->length = 0; + vPtr->interp = dataPtr->interp; + vPtr->hashPtr = NULL; + vPtr->chain = Blt_Chain_Create(); + vPtr->flush = FALSE; + vPtr->min = vPtr->max = Blt_NaN(); + vPtr->notifyFlags = NOTIFY_WHENIDLE; + vPtr->dataPtr = dataPtr; + return vPtr; +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_Vec_Free -- + * + * Removes the memory and frees resources associated with the vector. + * + * o Removes the trace and the TCL array variable and unsets + * the variable. + * o Notifies clients of the vector that the vector is being + * destroyed. + * o Removes any clients that are left after notification. + * o Frees the memory (if necessary) allocated for the array. + * o Removes the entry from the hash table of vectors. + * o Frees the memory allocated for the name. + * + * Results: + * None. + * + * Side effects: + * + *--------------------------------------------------------------------------- + */ +void +Blt_Vec_Free(Vector *vPtr) +{ + Blt_ChainLink link; + + if (vPtr->cmdToken != 0) { + DeleteCommand(vPtr); + } + if (vPtr->arrayName != NULL) { + UnmapVariable(vPtr); + } + vPtr->length = 0; + + /* Immediately notify clients that vector is going away */ + if (vPtr->notifyFlags & NOTIFY_PENDING) { + vPtr->notifyFlags &= ~NOTIFY_PENDING; + Tcl_CancelIdleCall(Blt_Vec_NotifyClients, vPtr); + } + vPtr->notifyFlags |= NOTIFY_DESTROYED; + Blt_Vec_NotifyClients(vPtr); + + for (link = Blt_Chain_FirstLink(vPtr->chain); link != NULL; + link = Blt_Chain_NextLink(link)) { + VectorClient *clientPtr; + + clientPtr = Blt_Chain_GetValue(link); + Blt_Free(clientPtr); + } + Blt_Chain_Destroy(vPtr->chain); + if ((vPtr->valueArr != NULL) && (vPtr->freeProc != TCL_STATIC)) { + if (vPtr->freeProc == TCL_DYNAMIC) { + Blt_Free(vPtr->valueArr); + } else { + (*vPtr->freeProc) ((char *)vPtr->valueArr); + } + } + if (vPtr->hashPtr != NULL) { + Blt_DeleteHashEntry(&vPtr->dataPtr->vectorTable, vPtr->hashPtr); + } +#ifdef NAMESPACE_DELETE_NOTIFY + if (vPtr->nsPtr != NULL) { + Blt_DestroyNsDeleteNotify(vPtr->interp, vPtr->nsPtr, vPtr); + } +#endif /* NAMESPACE_DELETE_NOTIFY */ + Blt_Free(vPtr); +} + +/* + *--------------------------------------------------------------------------- + * + * VectorInstDeleteProc -- + * + * Deletes the command associated with the vector. This is called only + * when the command associated with the vector is destroyed. + * + * Results: + * None. + * + *--------------------------------------------------------------------------- + */ +static void +VectorInstDeleteProc(ClientData clientData) +{ + Vector *vPtr = clientData; + + vPtr->cmdToken = 0; + Blt_Vec_Free(vPtr); +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_Vec_Create -- + * + * Creates a vector structure and the following items: + * + * o TCL command + * o TCL array variable and establishes traces on the variable + * o Adds a new entry in the vector hash table + * + * Results: + * A pointer to the new vector structure. If an error occurred NULL is + * returned and an error message is left in interp->result. + * + * Side effects: + * A new TCL command and array variable is added to the interpreter. + * + * ---------------------------------------------------------------------- + */ +Vector * +Blt_Vec_Create( + VectorInterpData *dataPtr, /* Interpreter-specific data. */ + const char *vecName, /* Namespace-qualified name of the vector */ + const char *cmdName, /* Name of the TCL command mapped to + * the vector */ + const char *varName, /* Name of the TCL array mapped to the + * vector */ + int *isNewPtr) +{ + Tcl_DString dString; + Vector *vPtr; + int isNew; + Blt_ObjectName objName; + char *qualName; + Blt_HashEntry *hPtr; + Tcl_Interp *interp = dataPtr->interp; + + isNew = 0; + vPtr = NULL; + + if (!Blt_ParseObjectName(interp, vecName, &objName, 0)) { + return NULL; + } + Tcl_DStringInit(&dString); + if ((objName.name[0] == '#') && (strcmp(objName.name, "#auto") == 0)) { + + do { /* Generate a unique vector name. */ + char string[200]; + + sprintf_s(string, 200, "vector%d", dataPtr->nextId++); + objName.name = string; + qualName = Blt_MakeQualifiedName(&objName, &dString); + hPtr = Blt_FindHashEntry(&dataPtr->vectorTable, qualName); + } while (hPtr != NULL); + } else { + const char *p; + + for (p = objName.name; *p != '\0'; p++) { + if (!VECTOR_CHAR(*p)) { + Tcl_AppendResult(interp, "bad vector name \"", objName.name, + "\": must contain digits, letters, underscore, or period", + (char *)NULL); + goto error; + } + } + qualName = Blt_MakeQualifiedName(&objName, &dString); + vPtr = Blt_Vec_ParseElement((Tcl_Interp *)NULL, dataPtr, qualName, + NULL, NS_SEARCH_CURRENT); + } + if (vPtr == NULL) { + hPtr = Blt_CreateHashEntry(&dataPtr->vectorTable, qualName, &isNew); + vPtr = Blt_Vec_New(dataPtr); + vPtr->hashPtr = hPtr; + vPtr->nsPtr = objName.nsPtr; + + vPtr->name = Blt_GetHashKey(&dataPtr->vectorTable, hPtr); +#ifdef NAMESPACE_DELETE_NOTIFY + Blt_CreateNsDeleteNotify(interp, objName.nsPtr, vPtr, + VectorInstDeleteProc); +#endif /* NAMESPACE_DELETE_NOTIFY */ + Blt_SetHashValue(hPtr, vPtr); + } + if (cmdName != NULL) { + Tcl_CmdInfo cmdInfo; + + if ((cmdName == vecName) || + ((cmdName[0] == '#') && (strcmp(cmdName, "#auto")==0))) { + cmdName = qualName; + } + if (Tcl_GetCommandInfo(interp, (char *)cmdName, &cmdInfo)) { + if (vPtr != cmdInfo.objClientData) { + Tcl_AppendResult(interp, "command \"", cmdName, + "\" already exists", (char *)NULL); + goto error; + } + /* We get here only if the old name is the same as the new. */ + goto checkVariable; + } + } + if (vPtr->cmdToken != 0) { + DeleteCommand(vPtr); /* Command already exists, delete old first */ + } + if (cmdName != NULL) { + Tcl_DString dString2; + + Tcl_DStringInit(&dString2); + if (cmdName != qualName) { + if (!Blt_ParseObjectName(interp, cmdName, &objName, 0)) { + goto error; + } + cmdName = Blt_MakeQualifiedName(&objName, &dString2); + } + vPtr->cmdToken = Tcl_CreateObjCommand(interp, (char *)cmdName, + Blt_Vec_InstCmd, vPtr, VectorInstDeleteProc); + Tcl_DStringFree(&dString2); + } + checkVariable: + if (varName != NULL) { + if ((varName[0] == '#') && (strcmp(varName, "#auto") == 0)) { + varName = qualName; + } + if (Blt_Vec_MapVariable(interp, vPtr, varName) != TCL_OK) { + goto error; + } + } + + Tcl_DStringFree(&dString); + *isNewPtr = isNew; + return vPtr; + + error: + Tcl_DStringFree(&dString); + if (vPtr != NULL) { + Blt_Vec_Free(vPtr); + } + return NULL; +} + + +int +Blt_Vec_Duplicate(Vector *destPtr, Vector *srcPtr) +{ + size_t nBytes; + size_t length; + + if (destPtr == srcPtr) { + /* Copying the same vector. */ + } + length = srcPtr->last - srcPtr->first + 1; + if (Blt_Vec_ChangeLength(destPtr->interp, destPtr, length) != TCL_OK) { + return TCL_ERROR; + } + nBytes = length * sizeof(double); + memcpy(destPtr->valueArr, srcPtr->valueArr + srcPtr->first, nBytes); + destPtr->offset = srcPtr->offset; + return TCL_OK; +} + + +/* + *--------------------------------------------------------------------------- + * + * VectorNamesOp -- + * + * Reports the names of all the current vectors in the interpreter. + * + * Results: + * A standard TCL result. interp->result will contain a list of + * all the names of the vector instances. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +VectorNamesOp( + ClientData clientData, /* Interpreter-specific data. */ + Tcl_Interp *interp, + int objc, + Tcl_Obj *const *objv) +{ + VectorInterpData *dataPtr = clientData; + Tcl_Obj *listObjPtr; + + listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **) NULL); + if (objc == 2) { + Blt_HashEntry *hPtr; + Blt_HashSearch cursor; + + for (hPtr = Blt_FirstHashEntry(&dataPtr->vectorTable, &cursor); + hPtr != NULL; hPtr = Blt_NextHashEntry(&cursor)) { + char *name; + + name = Blt_GetHashKey(&dataPtr->vectorTable, hPtr); + Tcl_ListObjAppendElement(interp, listObjPtr, + Tcl_NewStringObj(name, -1)); + } + } else { + Blt_HashEntry *hPtr; + Blt_HashSearch cursor; + + for (hPtr = Blt_FirstHashEntry(&dataPtr->vectorTable, &cursor); + hPtr != NULL; hPtr = Blt_NextHashEntry(&cursor)) { + char *name; + int i; + + name = Blt_GetHashKey(&dataPtr->vectorTable, hPtr); + for (i = 2; i < objc; i++) { + char *pattern; + + pattern = Tcl_GetString(objv[i]); + if (Tcl_StringMatch(name, pattern)) { + Tcl_ListObjAppendElement(interp, listObjPtr, + Tcl_NewStringObj(name, -1)); + break; + } + } + } + } + Tcl_SetObjResult(interp, listObjPtr); + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * VectorCreateOp -- + * + * Creates a TCL command, and array variable representing an instance of + * a vector. + * + * vector a + * vector b(20) + * vector c(-5:14) + * + * Results: + * A standard TCL result. + * + * Side effects: + * See the user documentation. + * + *--------------------------------------------------------------------------- + */ + +/*ARGSUSED*/ +static int +VectorCreate2( + ClientData clientData, /* Interpreter-specific data. */ + Tcl_Interp *interp, + int argStart, + int objc, + Tcl_Obj *const *objv) +{ + VectorInterpData *dataPtr = clientData; + Vector *vPtr; + int count, i; + CreateSwitches switches; + + /* + * Handle switches to the vector command and collect the vector name + * arguments into an array. + */ + count = 0; + vPtr = NULL; + for (i = argStart; i < objc; i++) { + char *string; + + string = Tcl_GetString(objv[i]); + if (string[0] == '-') { + break; + } + } + count = i - argStart; + if (count == 0) { + Tcl_AppendResult(interp, "no vector names supplied", (char *)NULL); + return TCL_ERROR; + } + memset(&switches, 0, sizeof(switches)); + if (Blt_ParseSwitches(interp, createSwitches, objc - i, objv + i, + &switches, BLT_SWITCH_DEFAULTS) < 0) { + return TCL_ERROR; + } + if (count > 1) { + if (switches.cmdName != NULL) { + Tcl_AppendResult(interp, + "can't specify more than one vector with \"-command\" switch", + (char *)NULL); + goto error; + } + if (switches.varName != NULL) { + Tcl_AppendResult(interp, + "can't specify more than one vector with \"-variable\" switch", + (char *)NULL); + goto error; + } + } + for (i = 0; i < count; i++) { + char *leftParen, *rightParen; + char *string; + int isNew; + int size, first, last; + + size = first = last = 0; + string = Tcl_GetString(objv[i + argStart]); + leftParen = strchr(string, '('); + rightParen = strchr(string, ')'); + if (((leftParen != NULL) && (rightParen == NULL)) || + ((leftParen == NULL) && (rightParen != NULL)) || + (leftParen > rightParen)) { + Tcl_AppendResult(interp, "bad vector specification \"", string, + "\"", (char *)NULL); + goto error; + } + if (leftParen != NULL) { + int result; + char *colon; + + *rightParen = '\0'; + colon = strchr(leftParen + 1, ':'); + if (colon != NULL) { + + /* Specification is in the form vecName(first:last) */ + *colon = '\0'; + result = Tcl_GetInt(interp, leftParen + 1, &first); + if ((*(colon + 1) != '\0') && (result == TCL_OK)) { + result = Tcl_GetInt(interp, colon + 1, &last); + if (first > last) { + Tcl_AppendResult(interp, "bad vector range \"", + string, "\"", (char *)NULL); + result = TCL_ERROR; + } + size = (last - first) + 1; + } + *colon = ':'; + } else { + /* Specification is in the form vecName(size) */ + result = Tcl_GetInt(interp, leftParen + 1, &size); + } + *rightParen = ')'; + if (result != TCL_OK) { + goto error; + } + if (size < 0) { + Tcl_AppendResult(interp, "bad vector size \"", string, "\"", + (char *)NULL); + goto error; + } + } + if (leftParen != NULL) { + *leftParen = '\0'; + } + /* + * By default, we create a TCL command by the name of the vector. + */ + vPtr = Blt_Vec_Create(dataPtr, string, + (switches.cmdName == NULL) ? string : switches.cmdName, + (switches.varName == NULL) ? string : switches.varName, &isNew); + if (leftParen != NULL) { + *leftParen = '('; + } + if (vPtr == NULL) { + goto error; + } + vPtr->freeOnUnset = switches.watchUnset; + vPtr->flush = switches.flush; + vPtr->offset = first; + if (size > 0) { + if (Blt_Vec_ChangeLength(interp, vPtr, size) != TCL_OK) { + goto error; + } + } + if (!isNew) { + if (vPtr->flush) { + Blt_Vec_FlushCache(vPtr); + } + Blt_Vec_UpdateClients(vPtr); + } + } + Blt_FreeSwitches(createSwitches, (char *)&switches, 0); + if (vPtr != NULL) { + /* Return the name of the last vector created */ + Tcl_SetStringObj(Tcl_GetObjResult(interp), vPtr->name, -1); + } + return TCL_OK; + error: + Blt_FreeSwitches(createSwitches, (char *)&switches, 0); + return TCL_ERROR; +} + +/* + *--------------------------------------------------------------------------- + * + * VectorCreateOp -- + * + * Creates a TCL command, and array variable representing an instance of + * a vector. + * + * vector a + * vector b(20) + * vector c(-5:14) + * + * Results: + * A standard TCL result. + * + * Side effects: + * See the user documentation. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +VectorCreateOp( + ClientData clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *const *objv) +{ + return VectorCreate2(clientData, interp, 2, objc, objv); +} + +/* + *--------------------------------------------------------------------------- + * + * VectorDestroyOp -- + * + * Destroys the vector and its related TCL command and array variable (if + * they exist). + * + * Results: + * A standard TCL result. + * + * Side effects: + * Deletes the vector. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +VectorDestroyOp( + ClientData clientData, /* Interpreter-specific data. */ + Tcl_Interp *interp, /* Not used. */ + int objc, + Tcl_Obj *const *objv) +{ + VectorInterpData *dataPtr = clientData; + Vector *vPtr; + int i; + + for (i = 2; i < objc; i++) { + if (Blt_Vec_LookupName(dataPtr, Tcl_GetString(objv[i]), &vPtr) + != TCL_OK) { + return TCL_ERROR; + } + Blt_Vec_Free(vPtr); + } + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * VectorExprOp -- + * + * Computes the result of the expression which may be either a scalar + * (single value) or vector (list of values). + * + * Results: + * A standard TCL result. + * + *--------------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static int +VectorExprOp( + ClientData clientData, /* Not Used. */ + Tcl_Interp *interp, + int objc, /* Not used. */ + Tcl_Obj *const *objv) +{ + return Blt_ExprVector(interp, Tcl_GetString(objv[2]), (Blt_Vector *)NULL); +} + +static Blt_OpSpec vectorCmdOps[] = +{ + {"create", 1, VectorCreateOp, 3, 0, + "vecName ?vecName...? ?switches...?",}, + {"destroy", 1, VectorDestroyOp, 3, 0, + "vecName ?vecName...?",}, + {"expr", 1, VectorExprOp, 3, 3, "expression",}, + {"names", 1, VectorNamesOp, 2, 3, "?pattern?...",}, +}; + +static int nCmdOps = sizeof(vectorCmdOps) / sizeof(Blt_OpSpec); + +/*ARGSUSED*/ +static int +VectorCmd( + ClientData clientData, /* Interpreter-specific data. */ + Tcl_Interp *interp, + int objc, + Tcl_Obj *const *objv) +{ + VectorCmdProc *proc; + /* + * Try to replicate the old vector command's behavior: + */ + if (objc > 1) { + char *string; + char c; + int i; + Blt_OpSpec *specPtr; + + string = Tcl_GetString(objv[1]); + c = string[0]; + for (specPtr = vectorCmdOps, i = 0; i < nCmdOps; i++, specPtr++) { + if ((c == specPtr->name[0]) && + (strcmp(string, specPtr->name) == 0)) { + goto doOp; + } + } + /* + * The first argument is not an operation, so assume that its + * actually the name of a vector to be created + */ + return VectorCreate2(clientData, interp, 1, objc, objv); + } + doOp: + /* Do the usual vector operation lookup now. */ + proc = Blt_GetOpFromObj(interp, nCmdOps, vectorCmdOps, BLT_OP_ARG1, + objc, objv,0); + if (proc == NULL) { + return TCL_ERROR; + } + return (*proc) (clientData, interp, objc, objv); +} + +/* + *--------------------------------------------------------------------------- + * + * VectorInterpDeleteProc -- + * + * This is called when the interpreter hosting the "vector" command + * is deleted. + * + * Results: + * None. + * + * Side effects: + * Destroys the math and index hash tables. In addition removes + * the hash table managing all vector names. + * + *--------------------------------------------------------------------------- + */ +/* ARGSUSED */ +static void +VectorInterpDeleteProc( + ClientData clientData, /* Interpreter-specific data. */ + Tcl_Interp *interp) +{ + VectorInterpData *dataPtr = clientData; + Blt_HashEntry *hPtr; + Blt_HashSearch cursor; + + for (hPtr = Blt_FirstHashEntry(&dataPtr->vectorTable, &cursor); + hPtr != NULL; hPtr = Blt_NextHashEntry(&cursor)) { + Vector *vPtr; + + vPtr = Blt_GetHashValue(hPtr); + vPtr->hashPtr = NULL; + Blt_Vec_Free(vPtr); + } + Blt_DeleteHashTable(&dataPtr->vectorTable); + + /* If any user-defined math functions were installed, remove them. */ + Blt_Vec_UninstallMathFunctions(&dataPtr->mathProcTable); + Blt_DeleteHashTable(&dataPtr->mathProcTable); + + Blt_DeleteHashTable(&dataPtr->indexProcTable); + Tcl_DeleteAssocData(interp, VECTOR_THREAD_KEY); + Blt_Free(dataPtr); +} + +VectorInterpData * +Blt_Vec_GetInterpData(Tcl_Interp *interp) +{ + VectorInterpData *dataPtr; + Tcl_InterpDeleteProc *proc; + + dataPtr = (VectorInterpData *) + Tcl_GetAssocData(interp, VECTOR_THREAD_KEY, &proc); + if (dataPtr == NULL) { + dataPtr = Blt_AssertMalloc(sizeof(VectorInterpData)); + dataPtr->interp = interp; + dataPtr->nextId = 0; + Tcl_SetAssocData(interp, VECTOR_THREAD_KEY, VectorInterpDeleteProc, + dataPtr); + Blt_InitHashTable(&dataPtr->vectorTable, BLT_STRING_KEYS); + Blt_InitHashTable(&dataPtr->mathProcTable, BLT_STRING_KEYS); + Blt_InitHashTable(&dataPtr->indexProcTable, BLT_STRING_KEYS); + Blt_Vec_InstallMathFunctions(&dataPtr->mathProcTable); + Blt_Vec_InstallSpecialIndices(&dataPtr->indexProcTable); +#ifdef HAVE_SRAND48 + srand48(time((time_t *) NULL)); +#endif + } + return dataPtr; +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_VectorCmdInitProc -- + * + * This procedure is invoked to initialize the "vector" command. + * + * Results: + * None. + * + * Side effects: + * Creates the new command and adds a new entry into a global Tcl + * associative array. + * + *--------------------------------------------------------------------------- + */ + +int +Blt_VectorCmdInitProc(Tcl_Interp *interp) +{ + static Blt_InitCmdSpec cmdSpec = {"vector", VectorCmd, }; + + cmdSpec.clientData = Blt_Vec_GetInterpData(interp); + return Blt_InitCmd(interp, "::blt", &cmdSpec); +} + + + +/* C Application interface to vectors */ + +/* + *--------------------------------------------------------------------------- + * + * Blt_CreateVector -- + * + * Creates a new vector by the name and size. + * + * Results: + * A standard TCL result. If the new array size is invalid or a vector + * already exists by that name, TCL_ERROR is returned. Otherwise TCL_OK + * is returned and the new vector is created. + * + * Side Effects: + * Memory will be allocated for the new vector. A new TCL command and + * Tcl array variable will be created. + * + *--------------------------------------------------------------------------- + */ + +/*LINTLIBRARY*/ +int +Blt_CreateVector2( + Tcl_Interp *interp, + const char *vecName, const char *cmdName, const char *varName, + int initialSize, + Blt_Vector **vecPtrPtr) +{ + VectorInterpData *dataPtr; /* Interpreter-specific data. */ + Vector *vPtr; + int isNew; + char *nameCopy; + + if (initialSize < 0) { + Tcl_AppendResult(interp, "bad vector size \"", Blt_Itoa(initialSize), + "\"", (char *)NULL); + return TCL_ERROR; + } + dataPtr = Blt_Vec_GetInterpData(interp); + + nameCopy = Blt_AssertStrdup(vecName); + vPtr = Blt_Vec_Create(dataPtr, nameCopy, cmdName, varName, &isNew); + Blt_Free(nameCopy); + + if (vPtr == NULL) { + return TCL_ERROR; + } + if (initialSize > 0) { + if (Blt_Vec_ChangeLength(interp, vPtr, initialSize) != TCL_OK) { + return TCL_ERROR; + } + } + if (vecPtrPtr != NULL) { + *vecPtrPtr = (Blt_Vector *) vPtr; + } + return TCL_OK; +} + +int +Blt_CreateVector( + Tcl_Interp *interp, + const char *name, + int size, + Blt_Vector **vecPtrPtr) +{ + return Blt_CreateVector2(interp, name, name, name, size, vecPtrPtr); +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_DeleteVector -- + * + * Deletes the vector of the given name. All clients with designated + * callback routines will be notified. + * + * Results: + * A standard TCL result. If no vector exists by that name, TCL_ERROR is + * returned. Otherwise TCL_OK is returned and vector is deleted. + * + * Side Effects: + * Memory will be released for the new vector. Both the TCL command and + * array variable will be deleted. All clients which set call back + * procedures will be notified. + * + *--------------------------------------------------------------------------- + */ +/*LINTLIBRARY*/ +int +Blt_DeleteVector(Blt_Vector *vecPtr) +{ + Vector *vPtr = (Vector *)vecPtr; + + Blt_Vec_Free(vPtr); + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_DeleteVectorByName -- + * + * Deletes the vector of the given name. All clients with designated + * callback routines will be notified. + * + * Results: + * A standard TCL result. If no vector exists by that name, TCL_ERROR is + * returned. Otherwise TCL_OK is returned and vector is deleted. + * + * Side Effects: + * Memory will be released for the new vector. Both the TCL command and + * array variable will be deleted. All clients which set call back + * procedures will be notified. + * + *--------------------------------------------------------------------------- + */ +/*LINTLIBRARY*/ +int +Blt_DeleteVectorByName(Tcl_Interp *interp, const char *name) +{ + VectorInterpData *dataPtr; /* Interpreter-specific data. */ + Vector *vPtr; + char *nameCopy; + int result; + + /* + * If the vector name was passed via a read-only string (e.g. "x"), the + * Blt_Vec_ParseElement routine will segfault when it tries to write into + * the string. Therefore make a writable copy and free it when we're + * done. + */ + nameCopy = Blt_AssertStrdup(name); + dataPtr = Blt_Vec_GetInterpData(interp); + result = Blt_Vec_LookupName(dataPtr, nameCopy, &vPtr); + Blt_Free(nameCopy); + + if (result != TCL_OK) { + return TCL_ERROR; + } + Blt_Vec_Free(vPtr); + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_VectorExists2 -- + * + * Returns whether the vector associated with the client token still + * exists. + * + * Results: + * Returns 1 is the vector still exists, 0 otherwise. + * + *--------------------------------------------------------------------------- + */ +int +Blt_VectorExists2(Tcl_Interp *interp, const char *vecName) +{ + VectorInterpData *dataPtr; /* Interpreter-specific data. */ + + dataPtr = Blt_Vec_GetInterpData(interp); + if (GetVectorObject(dataPtr, vecName, NS_SEARCH_BOTH) != NULL) { + return TRUE; + } + return FALSE; +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_VectorExists -- + * + * Returns whether the vector associated with the client token + * still exists. + * + * Results: + * Returns 1 is the vector still exists, 0 otherwise. + * + *--------------------------------------------------------------------------- + */ +int +Blt_VectorExists(Tcl_Interp *interp, const char *vecName) +{ + char *nameCopy; + int result; + + /* + * If the vector name was passed via a read-only string (e.g. "x"), the + * Blt_VectorParseName routine will segfault when it tries to write into + * the string. Therefore make a writable copy and free it when we're + * done. + */ + nameCopy = Blt_AssertStrdup(vecName); + result = Blt_VectorExists2(interp, nameCopy); + Blt_Free(nameCopy); + return result; +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_GetVector -- + * + * Returns a pointer to the vector associated with the given name. + * + * Results: + * A standard TCL result. If there is no vector "name", TCL_ERROR is + * returned. Otherwise TCL_OK is returned and vecPtrPtr will point to + * the vector. + * + *--------------------------------------------------------------------------- + */ +int +Blt_GetVector(Tcl_Interp *interp, const char *name, Blt_Vector **vecPtrPtr) +{ + VectorInterpData *dataPtr; /* Interpreter-specific data. */ + Vector *vPtr; + char *nameCopy; + int result; + + dataPtr = Blt_Vec_GetInterpData(interp); + /* + * If the vector name was passed via a read-only string (e.g. "x"), the + * Blt_VectorParseName routine will segfault when it tries to write into + * the string. Therefore make a writable copy and free it when we're + * done. + */ + nameCopy = Blt_AssertStrdup(name); + result = Blt_Vec_LookupName(dataPtr, nameCopy, &vPtr); + Blt_Free(nameCopy); + if (result != TCL_OK) { + return TCL_ERROR; + } + Blt_Vec_UpdateRange(vPtr); + *vecPtrPtr = (Blt_Vector *) vPtr; + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_GetVectorFromObj -- + * + * Returns a pointer to the vector associated with the given name. + * + * Results: + * A standard TCL result. If there is no vector "name", TCL_ERROR + * is returned. Otherwise TCL_OK is returned and vecPtrPtr will + * point to the vector. + * + *--------------------------------------------------------------------------- + */ +int +Blt_GetVectorFromObj( + Tcl_Interp *interp, + Tcl_Obj *objPtr, + Blt_Vector **vecPtrPtr) +{ + VectorInterpData *dataPtr; /* Interpreter-specific data. */ + Vector *vPtr; + + dataPtr = Blt_Vec_GetInterpData(interp); + if (Blt_Vec_LookupName(dataPtr, Tcl_GetString(objPtr), &vPtr) != TCL_OK) { + return TCL_ERROR; + } + Blt_Vec_UpdateRange(vPtr); + *vecPtrPtr = (Blt_Vector *) vPtr; + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_ResetVector -- + * + * Resets the vector data. This is called by a client to indicate that + * the vector data has changed. The vector does not need to point to + * different memory. Any clients of the vector will be notified of the + * change. + * + * Results: + * A standard TCL result. If the new array size is invalid, + * TCL_ERROR is returned. Otherwise TCL_OK is returned and the + * new vector data is recorded. + * + * Side Effects: + * Any client designated callbacks will be posted. Memory may + * be changed for the vector array. + * + *--------------------------------------------------------------------------- + */ +int +Blt_ResetVector( + Blt_Vector *vecPtr, + double *valueArr, /* Array containing the elements of the + * vector. If NULL, indicates to reset the + * vector.*/ + int length, /* The number of elements that the vector + * currently holds. */ + int size, /* The maximum number of elements that the + * array can hold. */ + Tcl_FreeProc *freeProc) /* Address of memory deallocation routine + * for the array of values. Can also be + * TCL_STATIC, TCL_DYNAMIC, or TCL_VOLATILE. */ +{ + Vector *vPtr = (Vector *)vecPtr; + + if (size < 0) { + Tcl_AppendResult(vPtr->interp, "bad array size", (char *)NULL); + return TCL_ERROR; + } + return Blt_Vec_Reset(vPtr, valueArr, length, size, freeProc); +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_ResizeVector -- + * + * Changes the size of the vector. All clients with designated callback + * routines will be notified of the size change. + * + * Results: + * A standard TCL result. If no vector exists by that name, TCL_ERROR is + * returned. Otherwise TCL_OK is returned and vector is resized. + * + * Side Effects: + * Memory may be reallocated for the new vector size. All clients which + * set call back procedures will be notified. + * + *--------------------------------------------------------------------------- + */ +int +Blt_ResizeVector(Blt_Vector *vecPtr, int length) +{ + Vector *vPtr = (Vector *)vecPtr; + + if (Blt_Vec_ChangeLength((Tcl_Interp *)NULL, vPtr, length) != TCL_OK) { + Tcl_AppendResult(vPtr->interp, "can't resize vector \"", vPtr->name, + "\"", (char *)NULL); + return TCL_ERROR; + } + if (vPtr->flush) { + Blt_Vec_FlushCache(vPtr); + } + Blt_Vec_UpdateClients(vPtr); + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_AllocVectorId -- + * + * Creates an identifier token for an existing vector. The identifier is + * used by the client routines to get call backs when (and if) the vector + * changes. + * + * Results: + * A standard TCL result. If "vecName" is not associated with a vector, + * TCL_ERROR is returned and interp->result is filled with an error + * message. + * + *--------------------------------------------------------------------------- + */ +Blt_VectorId +Blt_AllocVectorId(Tcl_Interp *interp, const char *name) +{ + VectorInterpData *dataPtr; /* Interpreter-specific data. */ + Vector *vPtr; + VectorClient *clientPtr; + Blt_VectorId clientId; + int result; + char *nameCopy; + + dataPtr = Blt_Vec_GetInterpData(interp); + /* + * If the vector name was passed via a read-only string (e.g. "x"), the + * Blt_VectorParseName routine will segfault when it tries to write into + * the string. Therefore make a writable copy and free it when we're + * done. + */ + nameCopy = Blt_AssertStrdup(name); + result = Blt_Vec_LookupName(dataPtr, nameCopy, &vPtr); + Blt_Free(nameCopy); + + if (result != TCL_OK) { + return (Blt_VectorId) 0; + } + /* Allocate a new client structure */ + clientPtr = Blt_AssertCalloc(1, sizeof(VectorClient)); + clientPtr->magic = VECTOR_MAGIC; + + /* Add the new client to the server's list of clients */ + clientPtr->link = Blt_Chain_Append(vPtr->chain, clientPtr); + clientPtr->serverPtr = vPtr; + clientId = (Blt_VectorId) clientPtr; + return clientId; +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_SetVectorChangedProc -- + * + * Sets the routine to be called back when the vector is changed or + * deleted. *clientData* will be provided as an argument. If *proc* is + * NULL, no callback will be made. + * + * Results: + * None. + * + * Side Effects: + * The designated routine will be called when the vector is changed + * or deleted. + * + *--------------------------------------------------------------------------- + */ +void +Blt_SetVectorChangedProc( + Blt_VectorId clientId, /* Client token identifying the vector */ + Blt_VectorChangedProc *proc,/* Address of routine to call when the contents + * of the vector change. If NULL, no routine + * will be called */ + ClientData clientData) /* One word of information to pass along when + * the above routine is called */ +{ + VectorClient *clientPtr = (VectorClient *)clientId; + + if (clientPtr->magic != VECTOR_MAGIC) { + return; /* Not a valid token */ + } + clientPtr->clientData = clientData; + clientPtr->proc = proc; +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_FreeVectorId -- + * + * Releases the token for an existing vector. This indicates that the + * client is no longer interested the vector. Any previously specified + * callback routine will no longer be invoked when (and if) the vector + * changes. + * + * Results: + * None. + * + * Side Effects: + * Any previously specified callback routine will no longer be + * invoked when (and if) the vector changes. + * + *--------------------------------------------------------------------------- + */ +void +Blt_FreeVectorId(Blt_VectorId clientId) +{ + VectorClient *clientPtr = (VectorClient *)clientId; + + if (clientPtr->magic != VECTOR_MAGIC) { + return; /* Not a valid token */ + } + if (clientPtr->serverPtr != NULL) { + /* Remove the client from the server's list */ + Blt_Chain_DeleteLink(clientPtr->serverPtr->chain, clientPtr->link); + } + Blt_Free(clientPtr); +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_NameOfVectorId -- + * + * Returns the name of the vector (and array variable). + * + * Results: + * The name of the array variable is returned. + * + *--------------------------------------------------------------------------- + */ +const char * +Blt_NameOfVectorId(Blt_VectorId clientId) +{ + VectorClient *clientPtr = (VectorClient *)clientId; + + if ((clientPtr->magic != VECTOR_MAGIC) || (clientPtr->serverPtr == NULL)) { + return NULL; + } + return clientPtr->serverPtr->name; +} + +const char * +Blt_NameOfVector(Blt_Vector *vecPtr) /* Vector to query. */ +{ + Vector *vPtr = (Vector *)vecPtr; + return vPtr->name; +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_VectorNotifyPending -- + * + * Returns the name of the vector (and array variable). + * + * Results: + * The name of the array variable is returned. + * + *--------------------------------------------------------------------------- + */ +int +Blt_VectorNotifyPending(Blt_VectorId clientId) +{ + VectorClient *clientPtr = (VectorClient *)clientId; + + if ((clientPtr == NULL) || (clientPtr->magic != VECTOR_MAGIC) || + (clientPtr->serverPtr == NULL)) { + return 0; + } + return (clientPtr->serverPtr->notifyFlags & NOTIFY_PENDING); +} + +/* + *--------------------------------------------------------------------------- + * + * Blt_GetVectorById -- + * + * Returns a pointer to the vector associated with the client + * token. + * + * Results: + * A standard TCL result. If the client token is not associated + * with a vector any longer, TCL_ERROR is returned. Otherwise, + * TCL_OK is returned and vecPtrPtr will point to vector. + * + *--------------------------------------------------------------------------- + */ +int +Blt_GetVectorById( + Tcl_Interp *interp, + Blt_VectorId clientId, /* Client token identifying the vector */ + Blt_Vector **vecPtrPtr) +{ + VectorClient *clientPtr = (VectorClient *)clientId; + + if (clientPtr->magic != VECTOR_MAGIC) { + Tcl_AppendResult(interp, "bad vector token", (char *)NULL); + return TCL_ERROR; + } + if (clientPtr->serverPtr == NULL) { + Tcl_AppendResult(interp, "vector no longer exists", (char *)NULL); + return TCL_ERROR; + } + Blt_Vec_UpdateRange(clientPtr->serverPtr); + *vecPtrPtr = (Blt_Vector *) clientPtr->serverPtr; + return TCL_OK; +} + +/*LINTLIBRARY*/ +void +Blt_InstallIndexProc(Tcl_Interp *interp, const char *string, + Blt_VectorIndexProc *procPtr) +{ + VectorInterpData *dataPtr; /* Interpreter-specific data. */ + Blt_HashEntry *hPtr; + int isNew; + + dataPtr = Blt_Vec_GetInterpData(interp); + hPtr = Blt_CreateHashEntry(&dataPtr->indexProcTable, string, &isNew); + if (procPtr == NULL) { + Blt_DeleteHashEntry(&dataPtr->indexProcTable, hPtr); + } else { + Blt_SetHashValue(hPtr, procPtr); + } +} + +/* spinellia@acm.org START */ + + +#define SWAP(a,b) tempr=(a);(a)=(b);(b)=tempr + +/* routine by Brenner + * data is the array of complex data points, perversely + * starting at 1 + * nn is the number of complex points, i.e. half the length of data + * isign is 1 for forward, -1 for inverse + */ +static void +four1(double *data, unsigned long nn, int isign) +{ + unsigned long n,mmax,m,j,istep,i; + double wtemp,wr,wpr,wpi,wi,theta; + double tempr,tempi; + + n=nn << 1; + j=1; + for (i = 1;i<n;i+=2) { + if (j > i) { + SWAP(data[j],data[i]); + SWAP(data[j+1],data[i+1]); + } + m=n >> 1; + while (m >= 2 && j > m) { + j -= m; + m >>= 1; + } + j += m; + } + mmax=2; + while (n > mmax) { + istep=mmax << 1; + theta=isign*(6.28318530717959/mmax); + wtemp=sin(0.5*theta); + wpr = -2.0*wtemp*wtemp; + wpi=sin(theta); + wr=1.0; + wi=0.0; + for (m=1;m<mmax;m+=2) { + for (i=m;i<=n;i+=istep) { + j=i+mmax; + tempr=wr*data[j]-wi*data[j+1]; + tempi=wr*data[j+1]+wi*data[j]; + data[j]=data[i]-tempr; + data[j+1]=data[i+1]-tempi; + data[i] += tempr; + data[i+1] += tempi; + } + wr=(wtemp=wr)*wpr-wi*wpi+wr; + wi=wi*wpr+wtemp*wpi+wi; + } + mmax=istep; + } +} +#undef SWAP + +static int +smallest_power_of_2_not_less_than(int x) +{ + int pow2 = 1; + + while (pow2 < x){ + pow2 <<= 1; + } + return pow2; +} + + +int +Blt_Vec_FFT( + Tcl_Interp *interp, /* Interpreter to report errors to */ + Vector *realPtr, /* If non-NULL, indicates to compute and + store the real values in this vector. */ + Vector *phasesPtr, /* If non-NULL, indicates to compute + * and store the imaginary values in + * this vector. */ + Vector *freqPtr, /* If non-NULL, indicates to compute + * and store the frequency values in + * this vector. */ + double delta, /* */ + int flags, /* Bit mask representing various + * flags: FFT_NO_constANT, + * FFT_SPECTRUM, and FFT_BARTLETT. */ + Vector *srcPtr) +{ + int length; + int pow2len; + double *paddedData; + int i; + double Wss = 0.0; + /* TENTATIVE */ + int middle = 1; + int noconstant; + + noconstant = (flags & FFT_NO_CONSTANT) ? 1 : 0; + + /* Length of the original vector. */ + length = srcPtr->last - srcPtr->first + 1; + /* new length */ + pow2len = smallest_power_of_2_not_less_than( length ); + + /* We do not do in-place FFTs */ + if (realPtr == srcPtr) { + Tcl_AppendResult(interp, "real vector \"", realPtr->name, + "\" can't be the same as the source", (char *)NULL); + return TCL_ERROR; + } + if (phasesPtr != NULL) { + if (phasesPtr == srcPtr) { + Tcl_AppendResult(interp, "imaginary vector \"", phasesPtr->name, + "\" can't be the same as the source", (char *)NULL); + return TCL_ERROR; + } + if (Blt_Vec_ChangeLength(interp, phasesPtr, + pow2len/2-noconstant+middle) != TCL_OK) { + return TCL_ERROR; + } + } + if (freqPtr != NULL) { + if (freqPtr == srcPtr) { + Tcl_AppendResult(interp, "frequency vector \"", freqPtr->name, + "\" can't be the same as the source", (char *)NULL); + return TCL_ERROR; + } + if (Blt_Vec_ChangeLength(interp, freqPtr, + pow2len/2-noconstant+middle) != TCL_OK) { + return TCL_ERROR; + } + } + + /* Allocate memory zero-filled array. */ + paddedData = Blt_Calloc(pow2len * 2, sizeof(double)); + if (paddedData == NULL) { + Tcl_AppendResult(interp, "can't allocate memory for padded data", + (char *)NULL); + return TCL_ERROR; + } + + /* + * Since we just do real transforms, only even locations will be + * filled with data. + */ + if (flags & FFT_BARTLETT) { /* Bartlett window 1 - ( (x - N/2) / (N/2) ) */ + double Nhalf = pow2len*0.5; + double Nhalf_1 = 1.0 / Nhalf; + double w; + + for (i = 0; i < length; i++) { + w = 1.0 - fabs( (i-Nhalf) * Nhalf_1 ); + Wss += w; + paddedData[2*i] = w * srcPtr->valueArr[i]; + } + for(/*empty*/; i < pow2len; i++) { + w = 1.0 - fabs((i-Nhalf) * Nhalf_1); + Wss += w; + } + } else { /* Squared window, i.e. no data windowing. */ + for (i = 0; i < length; i++) { + paddedData[2*i] = srcPtr->valueArr[i]; + } + Wss = pow2len; + } + + /* Fourier */ + four1(paddedData-1, pow2len, 1); + + /* + for(i=0;i<pow2len;i++){ + printf( "(%f %f) ", paddedData[2*i], paddedData[2*i+1] ); + } + */ + + /* the spectrum is the modulus of the transforms, scaled by 1/N^2 */ + /* or 1/(N * Wss) for windowed data */ + if (flags & FFT_SPECTRUM) { + double re, im, reS, imS; + double factor = 1.0 / (pow2len*Wss); + double *v = realPtr->valueArr; + + for (i = 0 + noconstant; i < pow2len / 2; i++) { + re = paddedData[2*i]; + im = paddedData[2*i+1]; + reS = paddedData[2*pow2len-2*i-2]; + imS = paddedData[2*pow2len-2*i-1]; + v[i - noconstant] = factor * ( +# if 0 + hypot( paddedData[2*i], paddedData[2*i+1] ) + + hypot( + paddedData[pow2len*2-2*i-2], + paddedData[pow2len*2-2*i-1] + ) +# else + sqrt( re*re + im* im ) + sqrt( reS*reS + imS*imS ) +# endif + ); + } + } else { + for(i = 0 + noconstant; i < pow2len / 2 + middle; i++) { + realPtr->valueArr[i - noconstant] = paddedData[2*i]; + } + } + if( phasesPtr != NULL ){ + for (i = 0 + noconstant; i < pow2len / 2 + middle; i++) { + phasesPtr->valueArr[i-noconstant] = paddedData[2*i+1]; + } + } + + /* Compute frequencies */ + if (freqPtr != NULL) { + double N = pow2len; + double denom = 1.0 / N / delta; + for( i=0+noconstant; i<pow2len/2+middle; i++ ){ + freqPtr->valueArr[i-noconstant] = ((double) i) * denom; + } + } + + /* Memory is necessarily dynamic, because nobody touched it ! */ + Blt_Free(paddedData); + + realPtr->offset = 0; + return TCL_OK; +} + + +int +Blt_Vec_InverseFFT(Tcl_Interp *interp, Vector *srcImagPtr, Vector *destRealPtr, + Vector *destImagPtr, Vector *srcPtr) +{ + int length; + int pow2len; + double *paddedData; + int i; + double oneOverN; + + if ((destRealPtr == srcPtr) || (destImagPtr == srcPtr )){ +/* we do not do in-place FFTs */ + return TCL_ERROR; + } + length = srcPtr->last - srcPtr->first + 1; + +/* minus one because of the magical middle element! */ + pow2len = smallest_power_of_2_not_less_than( (length-1)*2 ); + oneOverN = 1.0 / pow2len; + + if (Blt_Vec_ChangeLength(interp, destRealPtr, pow2len) != TCL_OK) { + return TCL_ERROR; + } + if (Blt_Vec_ChangeLength(interp, destImagPtr, pow2len) != TCL_OK) { + return TCL_ERROR; + } + + if( length != (srcImagPtr->last - srcImagPtr->first + 1) ){ + Tcl_AppendResult(srcPtr->interp, + "the length of the imagPart vector must ", + "be the same as the real one", (char *)NULL); + return TCL_ERROR; + } + + paddedData = Blt_AssertMalloc( pow2len*2*sizeof(double) ); + if( paddedData == NULL ){ + if (interp != NULL) { + Tcl_AppendResult(interp, "memory allocation failed", (char *)NULL); + } + return TCL_ERROR; + } + for(i=0;i<pow2len*2;i++) { paddedData[i] = 0.0; } + for(i=0;i<length-1;i++){ + paddedData[2*i] = srcPtr->valueArr[i]; + paddedData[2*i+1] = srcImagPtr->valueArr[i]; + paddedData[pow2len*2 - 2*i - 2 ] = srcPtr->valueArr[i+1]; + paddedData[pow2len*2 - 2*i - 1 ] = - srcImagPtr->valueArr[i+1]; + } +/* mythical middle element */ + paddedData[(length-1)*2] = srcPtr->valueArr[length-1]; + paddedData[(length-1)*2+1] = srcImagPtr->valueArr[length-1]; + +/* +for(i=0;i<pow2len;i++){ + printf( "(%f %f) ", paddedData[2*i], paddedData[2*i+1] ); +} + */ + +/* fourier */ + four1( paddedData-1, pow2len, -1 ); + +/* put values in their places, normalising by 1/N */ + for(i=0;i<pow2len;i++){ + destRealPtr->valueArr[i] = paddedData[2*i] * oneOverN; + destImagPtr->valueArr[i] = paddedData[2*i+1] * oneOverN; + } + +/* memory is necessarily dynamic, because nobody touched it ! */ + Blt_Free( paddedData ); + + return TCL_OK; +} + + +/* spinellia@acm.org STOP */ + + + +static double +FindSplit(Point2d *points, int i, int j, int *split) +{ + double maxDist2; + + maxDist2 = -1.0; + if ((i + 1) < j) { + int k; + double a, b, c; + + /* + * + * dist2 P(k) = | 1 P(i).x P(i).y | + * | 1 P(j).x P(j).y | + * | 1 P(k).x P(k).y | + * ------------------------------------------ + * (P(i).x - P(j).x)^2 + (P(i).y - P(j).y)^2 + */ + + a = points[i].y - points[j].y; + b = points[j].x - points[i].x; + c = (points[i].x * points[j].y) - (points[i].y * points[j].x); + for (k = (i + 1); k < j; k++) { + double dist2; + + dist2 = (points[k].x * a) + (points[k].y * b) + c; + if (dist2 < 0.0) { + dist2 = -dist2; + } + if (dist2 > maxDist2) { + maxDist2 = dist2; /* Track the maximum. */ + *split = k; + } + } + /* Correction for segment length---should be redone if can == 0 */ + maxDist2 *= maxDist2 / (a * a + b * b); + } + return maxDist2; +} + + +/* Douglas-Peucker line simplification algorithm */ +int +Blt_SimplifyLine(Point2d *inputPts, int low, int high, double tolerance, + int *indices) +{ +#define StackPush(a) s++, stack[s] = (a) +#define StackPop(a) (a) = stack[s], s-- +#define StackEmpty() (s < 0) +#define StackTop() stack[s] + int *stack; + int split = -1; + double dist2, tolerance2; + int s = -1; /* Points to top stack item. */ + int count; + + stack = Blt_AssertMalloc(sizeof(int) * (high - low + 1)); + StackPush(high); + count = 0; + indices[count++] = 0; + tolerance2 = tolerance * tolerance; + while (!StackEmpty()) { + dist2 = FindSplit(inputPts, low, StackTop(), &split); + if (dist2 > tolerance2) { + StackPush(split); + } else { + indices[count++] = StackTop(); + StackPop(low); + } + } + Blt_Free(stack); + return count; +} diff --git a/src/bltVector.h b/src/bltVector.h new file mode 100644 index 0000000..78eddc5 --- /dev/null +++ b/src/bltVector.h @@ -0,0 +1,137 @@ + +/* + * bltVector.h -- + * + * Copyright 1993-2004 George A Howlett. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY + * KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS + * OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef _BLT_VECTOR_H +#define _BLT_VECTOR_H + +typedef enum { + BLT_VECTOR_NOTIFY_UPDATE = 1, /* The vector's values has been updated */ + BLT_VECTOR_NOTIFY_DESTROY /* The vector has been destroyed and the client + * should no longer use its data (calling + * Blt_FreeVectorId) */ +} Blt_VectorNotify; + +typedef struct _Blt_VectorId *Blt_VectorId; + +typedef void (Blt_VectorChangedProc)(Tcl_Interp *interp, ClientData clientData, + Blt_VectorNotify notify); + +typedef struct { + double *valueArr; /* Array of values (possibly malloc-ed) */ + int numValues; /* Number of values in the array */ + int arraySize; /* Size of the allocated space */ + double min, max; /* Minimum and maximum values in the vector */ + int dirty; /* Indicates if the vector has been updated */ + int reserved; /* Reserved for future use */ + +} Blt_Vector; + +typedef double (Blt_VectorIndexProc)(Blt_Vector * vecPtr); + +typedef enum { + BLT_MATH_FUNC_SCALAR = 1, /* The function returns a single double + * precision value. */ + BLT_MATH_FUNC_VECTOR /* The function processes the entire vector. */ +} Blt_MathFuncType; + +/* + * To be safe, use the macros below, rather than the fields of the + * structure directly. + * + * The Blt_Vector is basically an opaque type. But it's also the + * actual memory address of the vector itself. I wanted to make the + * API as unobtrusive as possible. So instead of giving you a copy of + * the vector, providing various functions to access and update the + * vector, you get your hands on the actual memory (array of doubles) + * shared by all the vector's clients. + * + * The trade-off for speed and convenience is safety. You can easily + * break things by writing into the vector when other clients are + * using it. Use Blt_ResetVector to get around this. At least the + * macros are a reminder it isn't really safe to reset the data + * fields, except by the API routines. + */ +#define Blt_VecData(v) ((v)->valueArr) +#define Blt_VecLength(v) ((v)->numValues) +#define Blt_VecSize(v) ((v)->arraySize) +#define Blt_VecDirty(v) ((v)->dirty) + +BLT_EXTERN double Blt_VecMin(Blt_Vector *vPtr); +BLT_EXTERN double Blt_VecMax(Blt_Vector *vPtr); + +BLT_EXTERN Blt_VectorId Blt_AllocVectorId(Tcl_Interp *interp, + const char *vecName); + +BLT_EXTERN void Blt_SetVectorChangedProc(Blt_VectorId clientId, + Blt_VectorChangedProc *proc, ClientData clientData); + +BLT_EXTERN void Blt_FreeVectorId(Blt_VectorId clientId); + +BLT_EXTERN int Blt_GetVectorById(Tcl_Interp *interp, Blt_VectorId clientId, + Blt_Vector **vecPtrPtr); + +BLT_EXTERN const char *Blt_NameOfVectorId(Blt_VectorId clientId); + +BLT_EXTERN const char *Blt_NameOfVector(Blt_Vector *vecPtr); + +BLT_EXTERN int Blt_VectorNotifyPending(Blt_VectorId clientId); + +BLT_EXTERN int Blt_CreateVector(Tcl_Interp *interp, const char *vecName, + int size, Blt_Vector ** vecPtrPtr); + +BLT_EXTERN int Blt_CreateVector2(Tcl_Interp *interp, const char *vecName, + const char *cmdName, const char *varName, int initialSize, + Blt_Vector **vecPtrPtr); + +BLT_EXTERN int Blt_GetVector(Tcl_Interp *interp, const char *vecName, + Blt_Vector **vecPtrPtr); + +BLT_EXTERN int Blt_GetVectorFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, + Blt_Vector **vecPtrPtr); + +BLT_EXTERN int Blt_VectorExists(Tcl_Interp *interp, const char *vecName); + +BLT_EXTERN int Blt_ResetVector(Blt_Vector *vecPtr, double *dataArr, int n, + int arraySize, Tcl_FreeProc *freeProc); + +BLT_EXTERN int Blt_ResizeVector(Blt_Vector *vecPtr, int n); + +BLT_EXTERN int Blt_DeleteVectorByName(Tcl_Interp *interp, const char *vecName); + +BLT_EXTERN int Blt_DeleteVector(Blt_Vector *vecPtr); + +BLT_EXTERN int Blt_ExprVector(Tcl_Interp *interp, char *expr, + Blt_Vector *vecPtr); + +BLT_EXTERN void Blt_InstallIndexProc(Tcl_Interp *interp, const char *indexName, + Blt_VectorIndexProc * procPtr); + +BLT_EXTERN int Blt_VectorExists2(Tcl_Interp *interp, const char *vecName); + +#endif /* _BLT_VECTOR_H */ |