#
# SCS ID %W% %E%

set PI 3.141592653589793

# buildControls
#
# Build a control panel for a single pendulum. The panel will be created
# within the frame w and data will be stored in the array named by varName.

proc buildControls {w varName} {
    upvar #0 $varName pendulum

    # Setup up convenience variables

    set a $pendulum(a)
    set b $pendulum(b)

    # Create a canvas to display the pendulum trajectory and move the
    # origin to the centre of the canvas.

    set c [canvas $w.canvas -width 220 -height 220 \
	    -scrollregion {-110 -110 110 110}]

    # Add axes

    $c create line -100.0 0.0 100.0 0.0
    $c create line 0.0 -100.0 0.0 100.0

    # Now create the pseudo-ellipse using a Bezier-splined polygon

    set cmd "$c create polygon"
    set coords [ellipseCoords $a $b $pendulum(tilt)]
    set cmd [concat $cmd $coords -tags ellipse]
    eval $cmd

    # Configure it appropriately

    $c itemconfigure ellipse -outline $pendulum(colour) \
	-width 1 -fill {} -splinesteps 20 -smooth 1

    # Create the resize/rotate "handles"

    makeHandle $c 0 $a $b $pendulum(tilt) $varName $pendulum(colour)
    makeHandle $c 90 $a $b $pendulum(tilt) $varName white
    makeHandle $c 180 $a $b $pendulum(tilt) $varName white
    makeHandle $c 270 $a $b $pendulum(tilt) $varName white

    # Create the rotation direction arrow line. This is only approximately
    # correct at this point -- it will be drawn properly by showTrajectory

    $c create line $pendulum(a) -2 $pendulum(a) 2 \
	-arrow last -width 10 -tags arrow
    $c bind arrow <Button-1> "set Moving arrow"
    $c bind arrow <Double-Button-1> "\
	set $varName\(clockwise) \[expr {!\$[set varName]\(clockwise)}\];\
	showTrajectory $c $varName"
    $c bind arrow <ButtonRelease-1> "set Moving {}"

    # Global binding for B1-Motion on canvas

    bind $c <B1-Motion> "moveItem $c $varName %x %y"

    # Create a frame for the various controls

    set f [frame $w.f]

    # Each of the following controls sets an entry in the array named
    # by varName and calls showTrajectory to redraw the diagram of the
    # pendulum trajectory.

    # Entry for angle of tilt of the major ("a") axis

    parmEntry $f.angle {Major axis tilt (degrees)} 0.0 359.9 \
	[set varName](tilt) "showTrajectory $c $varName"

    # Entry for length of major axis

    parmEntry $f.axisa {Major axis} 0.0 100.0 \
	[set varName](a) "showTrajectory $c $varName"

    # Entry for length of minor ("b") axis

    parmEntry $f.axisb {Minor axis} 0.0 100.0 \
	[set varName](b) "showTrajectory $c $varName"

    # Entry for frequency of pendulum

    parmEntry $f.freq {Frequency} 0 25 \
	[set varName](freq) "showTrajectory $c $varName"

    # Entry for decay factor

    parmEntry $f.decay {Decay % per cycle} 0.0 99.9 \
	[set varName](decay) "showTrajectory $c $varName"

    # Entry for starting point in swing (the phase angle)

    parmEntry $f.phase {Phase angle (degrees)} 0.0 359.9 \
	[set varName](phase) "showTrajectory $c $varName"

    # Place the entries in the frame

    pack $f.angle -side top -expand no -fill x -padx 3 -pady 3
    pack $f.axisa -side top -expand no -fill x -padx 3 -pady 3
    pack $f.axisb -side top -expand no -fill x -padx 3 -pady 3
    pack $f.freq -side top -expand no -fill x -padx 3 -pady 3
    pack $f.decay -side top -expand no -fill x -padx 3 -pady 3
    pack $f.phase -side top -expand no -fill x -padx 3 -pady 3

    # Frame for direction radiobuttons

    frame $f.dir

    # Radiobutton for clockwise rotation

    radiobutton $f.dir.clock -text "Clockwise" -anchor e \
	-value 1 -variable [set varName](clockwise) \
	-command "showTrajectory $c $varName"

    # Radiobutton for anticlockwise

    radiobutton $f.dir.anti -text "Anti-clockwise" -anchor e \
	-value 0 -variable [set varName](clockwise) \
	-command "showTrajectory $c $varName"

    # Pack these

    pack $f.dir.clock -side left -expand no -fill x
    pack $f.dir.anti -side right -expand no -fill x
    pack $f.dir -side top -expand no -fill x

    # Pack the control frame at the top and the canvas at the bottom
    # and we are done!

    pack $f -side top -expand no -fill x
    pack $c -side bottom -expand yes -fill both

    # Force an update to make sure everything is calculated

    showTrajectory $c $varName

    # Add a pseudo-binding to the main window to enable a recalculation
    # and redisplay to be forced. This is used when loading new parameters
    # from a file.

    bind . <<ParameterChange>> +[list showTrajectory $c $varName]
}

# parmEntry
#
# A labelled entry box for entering numeric parameters. The callback
# "cmd" is invoked only when the user pauses typing for one second.

proc parmEntry {w label min max varName cmd} {
    global SavedParm
    upvar #0 $varName var

    set SavedParm($w) $var

    frame $w
    label $w.label -text $label
    entry $w.entry -textvariable $varName -width 5
    pack $w.entry -side right -expand no
    pack $w.label -side right -expand no -fill x -anchor e -padx 5

    bind $w.entry <Key> +[list set Display(stop) 1]
    bind $w.entry <FocusOut> +[list validateEntry $w $min $max $varName $cmd]
    bind $w.entry <Return> +[list validateEntry $w $min $max $varName $cmd]
}

proc validateEntry {w min max varName cmd} {
    global SavedParm
    upvar #0 $varName var

    if {[catch {expr {$var < $min || $var > $max}} result] || $result} {
	set var $SavedParm($w)
    } {
	set SavedParm($w) $var
	eval $cmd
    }
}

# degToRag
#
# Convert an angle in degrees to one in radians.

proc degToRad {degrees} {
    global PI

    return [expr {$degrees / 180.0 * $PI}]
}

# makeHandle
#
# Build a resize/rotate handle on the supplied canvas for the
# ellipse point at the specified angle

proc makeHandle {c angle a b tilt varName colour} {

    foreach {x y} [coordAtAngle $angle $a $b $tilt] {}

    $c create oval [expr {$x - 4}] [expr {$y - 4}] \
		    [expr {$x + 4}] [expr {$y + 4}] \
		    -tags "handle handle-$angle" -fill $colour -outline black

    $c bind handle-$angle <Button-1> "set Moving {$angle 1 1}"
    $c bind handle-$angle <Shift-Button-1> "set Moving {$angle 1 0}"
    $c bind handle-$angle <Control-Button-1> "set Moving {$angle 0 1}"
    $c bind handle-$angle <ButtonRelease-1> "set Moving {}"
}

proc moveItem {c varName x y} {
    global PI Moving
    upvar #0 $varName pendulum

    if {![info exists Moving] || $Moving == {}} {
	return
    }

    set cx [$c canvasx $x]
    set cy [$c canvasy $y]

    if {$Moving == "arrow"} {
	set a $pendulum(a)
	set b $pendulum(b)
	set tilt [degToRad $pendulum(tilt)]

	set xRot [expr {$cx * cos(-$tilt) + $cy * sin(-$tilt)}]
	set yRot [expr {$cy * cos(-$tilt) - $cx * sin(-$tilt)}]

	set rad [expr {atan2(-$a * $yRot, $b * $xRot)}]
	set pendulum(phase) [expr {(round($rad / $PI * 180.0) + 360) % 360}]

    } {
	set angle [lindex $Moving 0]
	set changeLength [lindex $Moving 1]
	set changeTilt [lindex $Moving 2]

	if {$changeLength} {
	    set len [expr {int(sqrt($cx * $cx + $cy * $cy))}]
	    if {$len > 100} {
		set len 100
	    }

	    switch -exact $angle {
		0 -
		180 {
		    set pendulum(a) $len
		}
		90 -
		270 {
		    set pendulum(b) $len
		}
	    }
	}

	if {$changeTilt} {
	    set rad [expr {atan2(-$cy, $cx)}]
	    set deg [expr {round($rad / $PI * 180.0 - $angle) % 360}]
	    if {$deg < 0} {
		incr deg 360
	    }
	    set pendulum(tilt) $deg
	}
    }

    showTrajectory $c $varName
}

# showTrajectory
#
# This procedure is called whenever the slider controls are altered to
# update the diagram of the pendulum trajectory in the control panel.
#
# The arguments are the path to the canvas widget, the name of the global
# array holding the data for this pendulum and then a dummy "catch all"
# used to receive the slider update data.

proc showTrajectory {w varName args} {
    global PI Display
    upvar #0 $varName pendulum

    # The global variable Display(stop) is used to halt any current
    # display run. If we come into this routine we have altered parameters
    # so any current display calculations will no longer be valid.

    set Display(stop) 1

    # Ensure that the minor axis stays less than the major axis!

    if {$pendulum(b) > $pendulum(a)} {
	set pendulum(b) $pendulum(a)
    }

    # Set up some shorthand names

    set a $pendulum(a)
    set b $pendulum(b)
    set phase $pendulum(phase)
    set tilt $pendulum(tilt)

    # Recalculate the ellipse coordinates

    set cmd "$w coords ellipse"
    set coords [ellipseCoords $a $b $tilt]
    set cmd [concat $cmd $coords]
    eval $cmd

    # Update phase/rotation direction arrowhead. We re-calculate the
    # ends of the line a small angle ahead and behind the phase point.

    set xy1 [coordAtAngle [expr {$phase + 4}] $a $b $tilt]
    set xy2 [coordAtAngle [expr {$phase - 4}] $a $b $tilt]

    if {$pendulum(clockwise)} {
	$w itemconfigure arrow -arrow last
    } {
	$w itemconfigure arrow -arrow first
    }
    eval "$w coords arrow $xy1 $xy2"

    # Now move all the resize handles

    foreach h {0 90 180 270} {

	# Find old centre of handle

	set coords [$w coords "handle-$h"]
	set oldx [expr {([lindex $coords 0] + [lindex $coords 2]) / 2}]
	set oldy [expr {([lindex $coords 1] + [lindex $coords 3]) / 2}]

	# Find new centre and calculate delta in position

	set newxy [coordAtAngle $h $a $b $tilt]
	set dx [expr {[lindex $newxy 0] - $oldx}]
	set dy [expr {[lindex $newxy 1] - $oldy}]

	# Move it!

	$w move "handle-$h" $dx $dy
    }
}

# coordAtAngle
#
# Calculate the (x, y) co-ordinates of a point on at "angle" degress
# around an ellipse with major axis "a" and minor axis "b", where
# the major axis is tilted at "tilt" degrees from the x-axis.
#
# This calculation is fundamental to understanding the rest of the
# program, particularly the ultimate calculation of the harmonograph
# display!

proc coordAtAngle {angle a b tilt} {

    set angle [degToRad $angle]
    set tilt [degToRad $tilt]

    # Basic coordinates for ellipse with major axis aligned along the
    # x-axis.

    set x [expr {$a * cos($angle)}]
    set y [expr {-$b * sin($angle)}]

    # Co-ordinate frame rotated by "tilt." Note that y-axis is "down"

    set xRot [expr {$x * cos($tilt) + $y * sin($tilt)}]
    set yRot [expr {$y * cos($tilt) - $x * sin($tilt)}]

    return [list $xRot $yRot]
}

# ellipseCoords
#
# Generate the co-ordinates for the control points of an Bezier-splined
# octagon that closely approximates an ellipse. This algorithm comes from
# Tclet written by ...

proc ellipseCoords {a b angle} {
    global PI

    set root2by2 0.707106781

    set ap [expr $a * 1.0823922]
    set bp [expr $b * 1.0823922]

    set rad [degToRad $angle]

    # RH end of major axis

    set x0 [expr cos($rad) * $ap]
    set y0 [expr -sin($rad) * $ap]

    # Top of minor axis

    set x2 [expr -sin($rad) * $bp]
    set y2 [expr -cos($rad) * $bp]

    # LH end of major axis

    set x4 [expr -cos($rad) * $ap]
    set y4 [expr sin($rad) * $ap]

    # Bottom of minor axis

    set x6 [expr sin($rad) * $bp]
    set y6 [expr cos($rad) * $bp]

    # Top-right control point

    set x1 [expr $root2by2 * ($x0 + $x2)]
    set y1 [expr $root2by2 * ($y0 + $y2)]

    # Top-left control point

    set x3 [expr $root2by2 * ($x2 + $x4)]
    set y3 [expr $root2by2 * ($y2 + $y4)]

    # Bottom-left control point

    set x5 [expr $root2by2 * ($x4 + $x6)]
    set y5 [expr $root2by2 * ($y4 + $y6)]

    # Bottom-right control point

    set x7 [expr $root2by2 * ($x6 + $x0)]
    set y7 [expr $root2by2 * ($y6 + $y0)]

    return [list \
	$x0 $y0 $x1 $y1 \
	$x2 $y2 $x3 $y3 \
	$x4 $y4 $x5 $y5 \
	$x6 $y6 $x7 $y7]
}

# controlPanel
#
# Create the "control panel" to allow the harmonograph parameters to be
# adjusted. This will either be as a new top-level window if run stand-alone
# or within a frame if run in a Tclet.

proc controlPanel {controls} {
    global Display Pendulum1 Pendulum2 Info

    if {![winfo exists $controls]} {
	if {$Info(tclet)} {
	    # Running in the plug-in so just create a frame

	    set top [frame $controls]
	} {
	    # Running stand-alone so create a new top-level window
	    # and menus

	    set top [toplevel $controls]
	    wm title $top "Harmonograph Controls"

	    # Create menu bar and menus

	    menu $top.mb -tearoff 0 -type menubar
	    $top configure -menu $top.mb

	    $top.mb add cascade -menu $top.mb.main -label "Window" -underline 0

	    menu $top.mb.main -tearoff 0
	    $top.mb.main add command -label "Close" -underline 0 \
		-command "wm withdraw $top"
	}

	# Create the "miscellaneous" controls frame -- this contains the
	# sliders to set the number of iterations and canvas size along
	# with colour-selection buttons.

	frame $top.misc -borderwidth 1 -relief sunken

	# A frame for the sliders

	set f [frame $top.misc.sliders]

	# Now the slider for the number of iterations

	scale $f.ticks -from 1 -to 100 -orient horizontal \
	    -variable Display(ticks) -sliderlength 15 -label {Iterations} \
	    -width 10

	# And one for the canvas size -- note that this runs in increments
	# of 50 pixels

	scale $f.size -from 100 -to 1000 -resolution 50 \
	    -orient horizontal -variable Display(size) -sliderlength 15 \
	    -label {Canvas size} -width 10

	# Pack them!

	pack $f.ticks -side left -expand yes -fill x
	pack $f.size -side right -expand yes -fill x
	pack $f -side top -expand no -fill x

	# Now the colour controls -- each one is a button that will bring
	# up the colour chooser along with a label showing the current
	# colour.

	set f [frame $top.misc.colours]

	label $f.bg -text {} -bg $Display(bg) -relief sunken
	button $f.setbg -text {Background ...} \
	    -command "set Display(bg) \[tk_chooseColor -initialcolor \$Display(bg)\];
	    $f.bg configure -bg \$Display(bg)"

	label $f.start -text {} -bg $Display(start) -relief sunken
	button $f.setstart -text {Start colour ...} \
	    -command "set Display(start) \[tk_chooseColor -initialcolor \$Display(start)\];
	    $f.start configure -bg \$Display(start)"

	label $f.end -text {} -bg $Display(end) -relief sunken
	button $f.setend -text {End colour ...} \
	    -command "set Display(end) \[tk_chooseColor -initialcolor \$Display(end)\];
	    $f.end configure -bg \$Display(end)"

	pack $f.setbg -side left -expand no -padx 5 -pady 5
	pack $f.bg -side left -expand yes -fill x -padx 5
	pack $f.setstart -side left -expand no -padx 5 -pady 5
	pack $f.start -side left -expand yes -fill x -padx 5
	pack $f.setend -side left -expand no -padx 5 -pady 5
	pack $f.end -side left -expand yes -fill x -padx 5

	pack $f -side bottom -expand no -fill x

	# Now create the two main pendulum controls

	frame $top.p1 -borderwidth 1 -relief sunken
	frame $top.p2 -borderwidth 1 -relief sunken

	buildControls $top.p1 Pendulum1
	buildControls $top.p2 Pendulum2

	# Pack everything

	pack $top.misc -side bottom -expand no -fill x
	pack $top.p1 -side left
	pack $top.p2 -side right
    }

    # The window now exists. So if this is not a Tclet just make sure
    # that it is visible

    if {!$Info(tclet)} {
	wm deiconify $controls
	raise $controls
    }
}

proc savePostScript {c colour} {
    set file [tk_getSaveFile -parent $c -title "Save PostScript" \
	-filetypes {{{PostScript files} {.ps .eps}} {{All files} {*}}}]

    if {$file == {}} {
	return
    }

    if {$colour} {
	set mode color
    } {
	set mode mono
	$c itemconfigure all -fill black
	$c configure -background white
    }

    $c postscript -file $file -colormode $mode
}

proc saveParameters {{file {}}} {
    global Display Pendulum1 Pendulum2

    if {$file == {}} {
	set file [tk_getSaveFile -title "Save As ..." \
	    -filetypes {{{Harmonograph files} {.hrm}} {{All files} {*}}} \
	    -defaultextension {.hrm}]
    }

    if {$file == {}} {
	return
    }

    if {[catch {open $file w} fh]} {
	tk_messageBox -type ok -icon error \
	    -title "Open Failed" -message "Can't open $file: $fh"
	return
    }

    puts $fh "# Harmonograph parameter file -- [clock format [clock seconds]]"
    puts $fh "array set Pendulum1 [list [array get Pendulum1]]"
    puts $fh "array set Pendulum2 [list [array get Pendulum2]]"
    puts $fh "array set Display [list [array get Display]]"

    close $fh
}

proc loadParameters {} {
    global Display Pendulum1 Pendulum2 Info

    set file [tk_getOpenFile -title "Open File ..." \
	-filetypes {{{Harmonograph files} {.hrm}} {{All files} {*}}} \
	-defaultextension {.hrm}]

    if {$file == {}} {
	return
    }

    if {[catch {source $file} msg]} {
	tk_messageBox -type ok -icon error \
	    -title "Open Failed" -message "Can't load $file: $msg"
	return
    }

    # Force an update of the control panel, if it exists, by generating
    # the ParameterChange pseudo-event. Bindings for this are installed
    # in buildControls.

    event generate . <<ParameterChange>>

    set Info(lastfile) $file
}

# harmonograph
#
# This is it, the main routine! This draws the harmonograph pattern on
# the canvas named by "canvas" using the parameters in the two global arrays
# passed in by name as name1 and name2. The global variable Display(stop) is
# used to signal that the current display run should stop.

proc harmonograph {canvas name1 name2} {
    upvar #0 $name1 p1
    upvar #0 $name2 p2
    global PI Display

    # Clear the canvas

    $canvas delete all

    # Resize and calculate x and y scalings

    $canvas configure -width $Display(size) -height $Display(size)
    set xmid [expr $Display(size) / 2]
    set ymid [expr $Display(size) / 2]
    $canvas configure -scrollregion "-$xmid -$ymid $xmid $ymid"
    set xscale [expr {double($Display(size)) / 400.0}]
    set yscale [expr {double($Display(size)) / 400.0}]

    # Set the background colour. We do this using a solid rectangle so
    # that the "postscript" option actually generates what is on screen --
    # otherwise the background colour is omitted.

    $canvas create rectangle -$xmid -$ymid $xmid $ymid -fill $Display(bg)

    # xlast and ylast record the co-ordinates of the last point drawn

    set xlast {}
    set ylast {}

    # The sgnN variables are used to change the direction of rotation
    # of the virtual pendulums

    if {$p1(clockwise)} {
	set sgn1 -1
    } {
	set sgn1 1
    }
    if {$p2(clockwise)} {
	set sgn2 -1
    } {
	set sgn2 1
    }

    # Pre-calculate some contant values to save unnecessary work
    # in the main body of the loop.

    set cos1 [expr {cos([degToRad $p1(tilt)])}]
    set sin1 [expr {sin([degToRad $p1(tilt)])}]
    set cos2 [expr {cos([degToRad $p2(tilt)])}]
    set sin2 [expr {sin([degToRad $p2(tilt)])}]

    set phase1 [degToRad $p1(phase)]
    set phase2 [degToRad $p2(phase)]

    # Figure out how many steps we will need per loop to make the
    # curves look reasonably smooth. If the pendulums are swinging
    # at high frequencies we need more steps ...

    set maxSteps [expr {($p1(freq) + $p2(freq)) * 50}]

    # Now calculate the decay factor for each pendulum. This is the factor
    # by which to reduce the amplitude on each calculation step such that a
    # single swing will result in a decay of the user-specified percentage.

    set decayFactor1 [expr {exp(log((100.0 - $p1(decay)) / 100.0) / (double($maxSteps) / double($p1(freq))))}]
    set decayFactor2 [expr {exp(log((100.0 - $p2(decay)) / 100.0) / (double($maxSteps) / double($p2(freq))))}]

    # This is the cumulative decay for each pendulum to date. This starts
    # at 1 and reduces on each swing.

    set decay1 1.0
    set decay2 1.0

    # Calculate colour change

    scan $Display(start) "#%2x%2x%2x" startRGB(r) startRGB(g) startRGB(b)
    scan $Display(end) "#%2x%2x%2x" endRGB(r) endRGB(g) endRGB(b)

    foreach c {r g b} {
	set deltaRGB($c) [expr {$endRGB($c) - $startRGB($c)}]
    }

    # Set the global variable to allow the run to commence

    set Display(stop) 0

    # One "tick" corresponds to one full swing of a pendulum at with
    # a frequency of 1 -- i.e. a "virtual second". Each "tick" is broken
    # down into "maxSteps" intervals.

    set totalSteps [expr {$Display(ticks) * $maxSteps}]
    set cumSteps 0

    for {set time 0} {!$Display(stop) && $time < $Display(ticks)} {incr time} {

	# Each "tick" is broken down into maxSteps intervals during which
	# a "unit length" pendulum would swing through an angle
	# theta = 2 * PI / maxSteps. The angles theta1 and theta2
	# correspond to the angles through which the two pendulums
	# will swing, given their frequencies.

	for {set step 0} {!$Display(stop) && $step < $maxSteps} {incr step; incr cumSteps} {

	    # Calculate the cumulative decay for each pendulum

	    set decay1 [expr {$decay1 * $decayFactor1}]
	    set decay2 [expr {$decay2 * $decayFactor2}]

	    # Now the angular distance around each swing

	    set theta [expr {2.0 * $PI * (double($cumSteps) / double($maxSteps))}]
	    set theta1 [expr {$theta * $sgn1}]
	    set theta2 [expr {$theta * $sgn2}]

	    # The main calculation of the (x, y) coordinates contributions
	    # of each pendulum (see also the routine coordAtAngle)

	    set x1 [expr {$p1(a) * cos($theta1 * $p1(freq) + $phase1)}]
	    set y1 [expr {-$p1(b) * sin($theta1 * $p1(freq) + $phase1)}]
	    set x1p [expr {($x1 * $cos1 + $y1 * $sin1) * $decay1}]
	    set y1p [expr {($y1 * $cos1 - $x1 * $sin1) * $decay1}]

	    set x2 [expr {$p2(a) * cos($theta2 * $p2(freq) + $phase2)}]
	    set y2 [expr {-$p2(b) * sin($theta2 * $p2(freq) + $phase2)}]
	    set x2p [expr {($x2 * $cos2 + $y2 * $sin2) * $decay2}]
	    set y2p [expr {($y2 * $cos2 - $x2 * $sin2) * $decay2}]

	    # Now the cumulative co-ordinates. Note that to simulate
	    # a real harmonograph these are *subtracted* because two
	    # pendulums swinging exactly in phase will produce no
	    # pattern.

	    set x [expr {($x1p - $x2p) * $xscale}]
	    set y [expr {($y1p - $y2p) * $yscale}]

	    # Now work out the desired colour of the current line
	    # segment.

	    set r [expr {$startRGB(r) + int($deltaRGB(r) * $cumSteps / $totalSteps)}]
	    set g [expr {$startRGB(g) + int($deltaRGB(g) * $cumSteps / $totalSteps)}]
	    set b [expr {$startRGB(b) + int($deltaRGB(b) * $cumSteps / $totalSteps)}]
	    set colour [format "#%02x%02x%02x" $r $g $b]

	    if {$xlast != {}} {
		$canvas create line $xlast $ylast $x $y -fill $colour
	    }
	    set xlast $x
	    set ylast $y
	    update
	}
    }
}

###
### Start Here
###

# Create global data.
#
# Pendulum1 and Pendulum2 hold, unsurprisingly, the parameters for the two
# pendulums while Display holds the other display-related values.

array set Pendulum1 {
    a		100
    b		80
    tilt	0
    clockwise	1
    colour	red
    freq	2
    decay	3
    phase	0
}

array set Pendulum2 {
    a		100
    b		30
    tilt	45
    clockwise	1
    colour	blue
    freq	3
    decay	3
    phase	30
}

array set Display {
    bg		#000000
    start	#0000ff
    end		#ff0000
    ticks	10
    size	400
}

# The Info array holds miscellaneous other global data

set Info(lastfile) {}		;# The name of the last file loaded
set Info(tclet) 0		;# Flag if running as a tclet

# Create the main display canvas

canvas .canvas -width $Display(size) -height $Display(size) -background $Display(bg)

# Now process the appropriate set of arguments and build either the
# single-pane Tclet of multi-window standalone version.

if {[info exists embed_args]} {
    set Info(tclet) 1

    # Build the control panel

    controlPanel .controls

    # Create a button to run the harmonograph

    button .run -text "Start" \
	-command {harmonograph .canvas Pendulum1 Pendulum2}

    # Pack it all

    pack .controls -side bottom -expand no -fill x
    pack .run -side bottom -expand no -padx 5 -pady 5 -anchor w
    pack .canvas -side top -expand yes -fill both

} {
    # Running stand-alone so build menu-bar and menus

    menu .mb -tearoff 0 -type menubar
    . configure -menu .mb

    menu .mb.main -tearoff 0

    .mb.main add command -label Run -underline 0 \
	-command "harmonograph .canvas Pendulum1 Pendulum2"
    .mb.main add command -label "Controls ..." -underline 0 \
	-command {controlPanel .controls}

    .mb.main add separator
    .mb.main add command -label "Open ..." -underline 0 \
	-command {loadParameters}
    .mb.main add command -label "Save ..." -underline 0 \
	-command {saveParameters $Info(lastfile)}
    .mb.main add command -label "Save As ..." -underline 5 \
	-command "saveParameters"
    .mb.main add cascade -label "Save PostScript" -underline 5 \
	-menu .mb.main.ps

    menu .mb.main.ps -tearoff 0
    .mb.main.ps add command -label "Colour ..." \
	-command "savePostScript .canvas 1" -underline 0
    .mb.main.ps add command -label "Monochrome ..." \
	-command "savePostScript .canvas 0" -underline 0

    .mb.main add separator

    .mb.main add command -label "Exit" -command exit -underline 1

    .mb add cascade -menu .mb.main -label Harmonograph -underline 1

    pack .canvas -side top -expand yes -fill both

    tkwait visibility .
    focus -force .
}
