# copyright (C) 1995-2004 Jean-Luc Fontaine (mailto:jfontain@free.fr)

package require Tk 8.3-
package require stooop


::stooop::class slice {
    variable PI 3.14159265358979323846
}

proc slice::slice {this canvas xRadius yRadius args} switched {$args} {
    # all parameter dimensions must be in pixels
    # note: all slice elements are tagged with slice($this)
    set ($this,canvas) $canvas
    set ($this,xRadius) $xRadius
    set ($this,yRadius) $yRadius
    switched::complete $this
    # wait till all options have been set for initial configuration
    complete $this
    update $this
}

proc slice::~slice {this} {
    if {[string length $switched::($this,-deletecommand)] > 0} {
        # always invoke command at global level
        uplevel #0 $switched::($this,-deletecommand)
    }
    $($this,canvas) delete slice($this)
}

proc slice::options {this} {
    return [list\
        [list -bottomcolor {} {}]\
        [list -deletecommand {} {}]\
        [list -height 0 0]\
        [list -scale {1 1} {1 1}]\
        [list -startandextent {0 0} {0 0}]\
        [list -topcolor {} {}]\
    ]
}

proc slice::set-height {this value} {      ;# not a dynamic option: see complete
    if {$switched::($this,complete)} {
        error {option -height cannot be set dynamically}
    }
}

proc slice::set-bottomcolor {this value} {
    if {![info exists ($this,startBottomArcFill)]} return
    set canvas $($this,canvas)
    $canvas itemconfigure $($this,startBottomArcFill)\
        -fill $value -outline $value
    $canvas itemconfigure $($this,startPolygon) -fill $value
    $canvas itemconfigure $($this,endBottomArcFill) -fill $value -outline $value
    $canvas itemconfigure $($this,endPolygon) -fill $value
}

proc slice::set-topcolor {this value} {
    if {![info exists ($this,topArc)]} return
    $($this,canvas) itemconfigure $($this,topArc) -fill $value
}

# data is stored at switched level
proc slice::set-deletecommand {this value} {}

proc slice::set-scale {this value} {
    if {$switched::($this,complete) && ($value > 0)} {
        # check for valid value following a non reproducible bug report
        update $this                   ;# requires initialization to be complete
    }
}

proc slice::set-startandextent {this value} {
    foreach {start extent} $value {}
    set ($this,start) [normalizedAngle $start]
    if {$extent < 0} {
        set ($this,extent) 0                 ;# a negative extent is meaningless
    } elseif {$extent >= 360} {
        # get as close as possible to 360, which would not work as it is
        # equivalent to 0
        set ($this,extent) [expr {360 - pow(10, -$::tcl_precision + 3)}]
    } else {
        set ($this,extent) $extent
    }
    if {$switched::($this,complete)} {
        update $this                   ;# requires initialization to be complete
    }
}

proc slice::normalizedAngle {value} {
    # normalize value between -180 and 180 degrees (not included)
    while {$value >= 180} {
        set value [expr {$value - 360}]
    }
    while {$value < -180} {
        set value [expr {$value + 360}]
    }
    return $value
}

proc slice::complete {this} {
    set canvas $($this,canvas)
    set xRadius $($this,xRadius)
    set yRadius $($this,yRadius)
    set bottomColor $switched::($this,-bottomcolor)
    # use an empty image as an origin marker with only 2 coordinates
    set ($this,origin)\
        [$canvas create image -$xRadius -$yRadius -tags slice($this)]
    if {$switched::($this,-height) > 0} {                                  ;# 3D
        set ($this,startBottomArcFill) [$canvas create arc\
            0 0 0 0 -style chord -extent 0 -fill $bottomColor\
            -outline $bottomColor -tags slice($this)\
        ]
        set ($this,startPolygon) [$canvas create polygon 0 0 0 0 0 0\
            -fill $bottomColor -tags slice($this)\
        ]
        set ($this,startBottomArc) [$canvas create arc 0 0 0 0\
            -style arc -extent 0 -fill black -tags slice($this)\
        ]
        set ($this,endBottomArcFill) [$canvas create arc 0 0 0 0\
            -style chord -extent 0 -fill $bottomColor\
            -outline $bottomColor -tags slice($this)\
        ]
        set ($this,endPolygon) [$canvas create polygon 0 0 0 0 0 0\
            -fill $bottomColor -tags slice($this)\
        ]
        set ($this,endBottomArc) [$canvas create arc 0 0 0 0\
            -style arc -extent 0 -fill black -tags slice($this)\
        ]
        set ($this,startLeftLine)\
            [$canvas create line 0 0 0 0 -tags slice($this)]
        set ($this,startRightLine)\
            [$canvas create line 0 0 0 0 -tags slice($this)]
        set ($this,endLeftLine) [$canvas create line 0 0 0 0 -tags slice($this)]
        set ($this,endRightLine)\
            [$canvas create line 0 0 0 0 -tags slice($this)]
    }
    set ($this,topArc) [$canvas create arc\
        -$xRadius -$yRadius $xRadius $yRadius\
        -fill $switched::($this,-topcolor) -tags slice($this)\
    ]
    # move slice so upper-left corner is at requested coordinates
    $canvas move slice($this) $xRadius $yRadius
}

proc slice::update {this} {
    set canvas $($this,canvas)
    # first store slice position in case it was moved as a whole
    set coordinates [$canvas coords $($this,origin)]
    set xRadius $($this,xRadius)
    set yRadius $($this,yRadius)
    $canvas coords $($this,origin) -$xRadius -$yRadius
    $canvas coords $($this,topArc) -$xRadius -$yRadius $xRadius $yRadius
    $canvas itemconfigure $($this,topArc)\
        -start $($this,start) -extent $($this,extent)
    if {$switched::($this,-height) > 0} {                                  ;# 3D
        updateBottom $this
    }
    # now position slice at the correct coordinates
    $canvas move slice($this) [expr {[lindex $coordinates 0] + $xRadius}]\
        [expr {[lindex $coordinates 1] + $yRadius}]
    # finally apply scale
    eval $canvas scale slice($this) $coordinates $switched::($this,-scale)
}

proc slice::updateBottom {this} {
    variable PI

    set start $($this,start)
    set extent $($this,extent)

    set canvas $($this,canvas)
    set xRadius $($this,xRadius)
    set yRadius $($this,yRadius)
    set height $switched::($this,-height)

    # first make all bottom parts invisible
    $canvas itemconfigure $($this,startBottomArcFill) -extent 0
    $canvas coords $($this,startBottomArcFill)\
        -$xRadius -$yRadius $xRadius $yRadius
    $canvas move $($this,startBottomArcFill) 0 $height
    $canvas itemconfigure $($this,startBottomArc) -extent 0
    $canvas coords $($this,startBottomArc) -$xRadius -$yRadius $xRadius $yRadius
    $canvas move $($this,startBottomArc) 0 $height
    $canvas coords $($this,startLeftLine) 0 0 0 0
    $canvas coords $($this,startRightLine) 0 0 0 0
    $canvas itemconfigure $($this,endBottomArcFill) -extent 0
    $canvas coords $($this,endBottomArcFill)\
        -$xRadius -$yRadius $xRadius $yRadius
    $canvas move $($this,endBottomArcFill) 0 $height
    $canvas itemconfigure $($this,endBottomArc) -extent 0
    $canvas coords $($this,endBottomArc) -$xRadius -$yRadius $xRadius $yRadius
    $canvas move $($this,endBottomArc) 0 $height
    $canvas coords $($this,endLeftLine) 0 0 0 0
    $canvas coords $($this,endRightLine) 0 0 0 0
    $canvas coords $($this,startPolygon) 0 0 0 0 0 0 0 0
    $canvas coords $($this,endPolygon) 0 0 0 0 0 0 0 0

    set startX [expr {$xRadius * cos($start * $PI / 180)}]
    set startY [expr {-$yRadius * sin($start * $PI / 180)}]
    set end [normalizedAngle [expr {$start + $extent}]]
    set endX [expr {$xRadius * cos($end * $PI / 180)}]
    set endY [expr {-$yRadius * sin($end * $PI / 180)}]

    set startBottom [expr {$startY + $height}]
    set endBottom [expr {$endY + $height}]

    if {(($start >= 0) && ($end >= 0)) || (($start < 0) && ($end < 0))} {
        # start and end angles are on the same side of the 0 abscissa
        if {$extent <= 180} {                ;# slice size is less than half pie
            if {$start < 0} {    ;# slice is facing viewer, so bottom is visible
                $canvas itemconfigure $($this,startBottomArcFill)\
                    -start $start -extent $extent
                $canvas itemconfigure $($this,startBottomArc)\
                    -start $start -extent $extent
                # only one polygon is needed
                $canvas coords $($this,startPolygon)\
                    $startX $startY $endX $endY\
                    $endX $endBottom $startX $startBottom
                $canvas coords $($this,startLeftLine)\
                    $startX $startY $startX $startBottom
                $canvas coords $($this,startRightLine)\
                    $endX $endY $endX $endBottom
            }                                        ;# else only top is visible
        } else {                             ;# slice size is more than half pie
            if {$start < 0} {
                # slice opening is facing viewer, so bottom is in 2 parts
                $canvas itemconfigure $($this,startBottomArcFill)\
                    -start 0 -extent $start
                $canvas itemconfigure $($this,startBottomArc)\
                    -start 0 -extent $start
                $canvas coords $($this,startPolygon)\
                    $startX $startY $xRadius 0\
                    $xRadius $height $startX $startBottom
                $canvas coords $($this,startLeftLine)\
                    $startX $startY $startX $startBottom
                $canvas coords $($this,startRightLine)\
                    $xRadius 0 $xRadius $height

                set bottomArcExtent [expr {$end + 180}]
                $canvas itemconfigure $($this,endBottomArcFill)\
                    -start -180 -extent $bottomArcExtent
                $canvas itemconfigure $($this,endBottomArc)\
                    -start -180 -extent $bottomArcExtent
                $canvas coords $($this,endPolygon)\
                    -$xRadius 0 $endX $endY $endX $endBottom -$xRadius $height
                $canvas coords $($this,endLeftLine)\
                    -$xRadius 0 -$xRadius $height
                $canvas coords $($this,endRightLine)\
                    $endX $endY $endX $endBottom
            } else {
                # slice back is facing viewer, so bottom occupies half the pie
                $canvas itemconfigure $($this,startBottomArcFill)\
                    -start 0 -extent -180
                $canvas itemconfigure $($this,startBottomArc)\
                    -start 0 -extent -180
                # only one polygon is needed
                $canvas coords $($this,startPolygon)\
                    -$xRadius 0 $xRadius 0 $xRadius $height -$xRadius $height
                $canvas coords $($this,startLeftLine)\
                    -$xRadius 0 -$xRadius $height
                $canvas coords $($this,startRightLine)\
                    $xRadius 0 $xRadius $height
            }
        }
    } else {     ;# start and end angles are on opposite sides of the 0 abscissa
        if {$start < 0} {                        ;# slice start is facing viewer
            $canvas itemconfigure $($this,startBottomArcFill)\
                -start 0 -extent $start
            $canvas itemconfigure $($this,startBottomArc)\
                -start 0 -extent $start
            # only one polygon is needed
            $canvas coords $($this,startPolygon) $startX $startY $xRadius 0\
                $xRadius $height $startX $startBottom
            $canvas coords $($this,startLeftLine)\
                $startX $startY $startX $startBottom
            $canvas coords $($this,startRightLine) $xRadius 0 $xRadius $height
        } else {                                   ;# slice end is facing viewer
            set bottomArcExtent [expr {$end + 180}]
            $canvas itemconfigure $($this,endBottomArcFill)\
                -start -180 -extent $bottomArcExtent
            $canvas itemconfigure $($this,endBottomArc)\
                -start -180 -extent $bottomArcExtent
            # only one polygon is needed
            $canvas coords $($this,endPolygon)\
                -$xRadius 0 $endX $endY $endX $endBottom -$xRadius $height
            $canvas coords $($this,startLeftLine) -$xRadius 0 -$xRadius $height
            $canvas coords $($this,startRightLine) $endX $endY $endX $endBottom
        }
    }
}

proc slice::rotate {this angle} {
    if {$angle == 0} return
    set ($this,start) [normalizedAngle [expr {$($this,start) + $angle}]]
    update $this
}

# return actual sizes and positions after scaling
proc slice::data {this arrayName} {
    upvar 1 $arrayName data

    set data(start) $($this,start)
    set data(extent) $($this,extent)
    foreach {x y} $switched::($this,-scale) {}
    set data(xRadius) [expr {$x * $($this,xRadius)}]
    set data(yRadius) [expr {$y * $($this,yRadius)}]
    set data(height) [expr {$y * $switched::($this,-height)}]
    foreach {x y} [$($this,canvas) coords $($this,origin)] {}
    set data(xCenter) [expr {$x + $data(xRadius)}]
    set data(yCenter) [expr {$y + $data(yRadius)}]
}
