Arjen Markus (19 december 2011) I am fascinated by mathematics in general and geometry in particular. So, this weekend I extended my old mathematical notebook with the specific purpose of visualising classical geometrical constructions. For this I needed an extended version - which you will find below (some convenience procedures and a different user-interface).
The idea is to use the flexibility of the text widget to allow for an interactive display of the construction: via tags the example below highlights the steps in the construction of lines tangent to a circles. All in the classical tradition of the ancient Greek.
I use the tag mechanism of the canvas to delete those items I do not need anymore - right now: just used by the "Reset" button.
(Note: it is not perfect yet - opening a second file for instance does not refresh the text widget properly)
Here is a screenshot:
# Classical construction:
# Tangent lines to a circle
#
<h1>Using compasses and straightedge</h1>
<p>
The classical tools for geometrical constructions, such as the hexagon
or the bissectrix of an angle are the compasses and the straightedge.
<p>
Here we show how to use these tools to construct the lines through a
given point that are tangent to a circle. The construction is shown in
steps.
(This illustrates that it is easy to make interactive displays).
<p>
First we draw the point P and the circle C (see the canvas on the
right-hand side)
<p>
Then we take the following steps (press the Next button):
<ul>
<tag>step1
<li>
Draw a line piece through the centre of the circle and the point.
<tag>step2
<li>
Construct the line through the middle of this line piece, perpendicular
to that line.
<tag>step3
<li>
The midpoint is the centre of a circle through the centre of C and P.
<tag>step4
<li>
Finally the points where the two circles intersect are the points where
the two tangents lines meet circle C.
<tag>step5
<li>
Draw the tangent lines.
</ul>
<tag>normal
This way we have constructed the tangent lines.
@init {
proc resetTags {} {
variable TXT
variable fontNormal
$TXT tag configure step1 -foreground lightgrey -font $fontNormal
$TXT tag configure step2 -foreground lightgrey -font $fontNormal
$TXT tag configure step3 -foreground lightgrey -font $fontNormal
$TXT tag configure step4 -foreground lightgrey -font $fontNormal
$TXT tag configure step5 -foreground lightgrey -font $fontNormal
}
resetTags
proc showStep {step} {
variable CNV
variable xcentre
variable ycentre
variable radius
variable xpoint
variable ypoint
variable xp ;# Midpoint between point P and centre of circle C
variable yp
variable radiusM ;# Radius of the circle through P and centre
variable tangentPoints
switch $step {
"0" {
$CNV delete steps
}
"1" {
#
# Step: line piece connecting point and centre
#
set id [polyline [list $xcentre $ycentre $xpoint $ypoint]]
$CNV itemconfigure $id -tag steps
}
"2" {
#
# Midpoint of the line piece
#
set id1 [circle $xcentre $ycentre 1.5]
set id2 [circle $xpoint $ypoint 1.5]
$CNV itemconfigure $id1 -tag steps
$CNV itemconfigure $id2 -tag steps
#
# Compute the two intersection points
# and draw them, as well as the line piece between them
#
set points [circleIntersection [list $xcentre $ycentre 1.5] \
[list $xpoint $ypoint 1.5]]
set id [polyline $points]
$CNV itemconfigure $id -tag steps
set xp [expr {([lindex $points 0] + [lindex $points 2]) / 2.0}]
set yp [expr {([lindex $points 1] + [lindex $points 3]) / 2.0}]
set id1 [point [lindex $points 0] [lindex $points 1] black]
set id2 [point [lindex $points 2] [lindex $points 3] black]
set id3 [point $xp $yp red]
$CNV itemconfigure $id1 -tag steps
$CNV itemconfigure $id2 -tag steps
$CNV itemconfigure $id3 -tag steps
}
"3" {
#
# Step: circle through centre and point
#
set radiusM [expr {hypot($xcentre-$xp,$ycentre-$yp)}]
set id [circle $xp $yp $radiusM red]
$CNV itemconfigure $id -tag steps
}
"4" {
#
# Step: intersection points of the two circles
#
set tangentPoints [circleIntersection [list $xcentre $ycentre $radius] \
[list $xp $yp $radiusM]]
set id1 [point [lindex $tangentPoints 0] [lindex $tangentPoints 1] green]
set id2 [point [lindex $tangentPoints 2] [lindex $tangentPoints 3] green]
$CNV itemconfigure $id1 -tag steps
$CNV itemconfigure $id2 -tag steps
}
"5" {
#
# Step: draw the tangent lines
#
set id1 [infiniteLine [lindex $tangentPoints 0] [lindex $tangentPoints 1] $xpoint $ypoint green]
set id2 [infiniteLine [lindex $tangentPoints 2] [lindex $tangentPoints 3] $xpoint $ypoint green]
$CNV itemconfigure $id1 -tag steps -width 2
$CNV itemconfigure $id2 -tag steps -width 2
}
}
}
}
@canvasright 400 400 {
variable state
variable xcentre
variable ycentre
variable xpoint
variable ypoint
variable radius
variable CNV
set state 0
scale {-2.5 -2.0 1.5 2.0}
set xcentre -1.0
set ycentre 0.0
set xpoint 0.7
set ypoint 0.6
set radius 0.8
set id [circle $xcentre $ycentre $radius black] ;# Circle C
$CNV itemconfigure $id -width 2
point $xpoint $ypoint black ;# Point P
set state 0
}
@button Reset {
resetTags
$CNV delete steps
set state 0
}
@button Next {
variable state
resetTags
incr state
$TXT tag configure step$state -foreground black -font $fontBold
showStep $state
}Here is a second example, as it presents an animated construction, you will have to run it yourself to see it.
# witch_agnesi.txt --
# Construct the curve known as the witch of Agnesi
#
<h1>Witch of Agnesi</h1>
<p>
With compasses and straightedge you can construct all manner of curves,
though sometimes the process is more mechanical than mathematical.
<p>
Here is an example: the witch of Agnesi is constructed by drawing
a line through a fixed point on a circle and using the intersection
through that circle and a tangent line to define a new point.
<p>
The process is illustrated in the figure on the right. Press the
"Go" button to see how it works.
<p>
The curve has the parametric form:
<pre>
x = 2 a cot t
y = a (1-cos(2t))
</pre>
where "a" is the radius of the circle (cf. mathworld.wolfram.com/WitchofAgnesi.html).
@init {
proc intersectionCircleLine {circle line} {
foreach {xc yc radius} $circle {break}
foreach {x1 y1 x2 y2} $line {break}
set dx [expr {$x2 - $x1}]
set dy [expr {$y2 - $y1}]
set length [expr {hypot($dx,$dy)}]
set xn [expr {-$dy/$length}]
set yn [expr {$dx/$length}]
set mu [expr {($x1-$xc)*$xn + ($y1-$yc)*$yn}]
set xmid [expr {$xc + $mu * $xn}]
set ymid [expr {$yc + $mu * $yn}]
set dist [expr {sqrt($radius**2 - ($xmid-$xc)**2 - ($ymid-$yc)**2)/$length}]
set xi1 [expr {$xmid + $dx * $dist}]
set yi1 [expr {$ymid + $dy * $dist}]
set xi2 [expr {$xmid - $dx * $dist}]
set yi2 [expr {$ymid - $dy * $dist}]
return [list $xi1 $yi1 $xi2 $yi2]
}
proc drawWitch {x} {
variable CNV
variable coords
$CNV delete line
$CNV delete witch
set line [list 0.0 -1.0 $x 1.0]
set id1 [polyline $line black]
set id2 [point $x 1.0 red]
$CNV itemconfigure $id1 -tag line
$CNV itemconfigure $id2 -tag line
#
# Determine the points of intersection and select the
# right one - the one with y > -1.0
#
set intersectionPoints [intersectionCircleLine {0.0 0.0 1.0} $line]
foreach {xp yp} $intersectionPoints {
if { $yp > -0.999 } {
break
}
}
#
# Draw the auxiliary lines
#
set id1 [polyline [list $x 1.0 $x -1.0] red]
set id2 [polyline [list $xp $yp $x $yp] red]
set id3 [point $xp $yp red]
set id4 [point $x $yp red]
$CNV itemconfigure $id1 -tag line
$CNV itemconfigure $id2 -tag line
$CNV itemconfigure $id3 -tag line
$CNV itemconfigure $id4 -tag line
lappend coords $x $yp
if { [llength $coords] > 2 } {
set id [polyline $coords red]
$CNV itemconfigure $id -tag witch
}
#
# Note: the procedure is defined within the MathData namespace
#
if { $x < 4.0 } {
after 100 [list ::MathData::drawWitch [expr {$x+0.1}]]
} else {
$CNV delete line
$CNV itemconfigure $id -width 2
}
}
}
@canvasright 400 400 {
scale {-4.0 -4.0 4.0 4.0}
infiniteLine -2.0 1.0 2.0 1.0 black
circle 0.0 0.0 1.0 black
point 0.0 -1.0 black
console show
puts [intersectionCircleLine {0.0 0.0 1.0} {0.0 0.0 0.0 1.0}]
}
@button Go {
variable coords
set coords {}
drawWitch -4.0
}# mathbook.tcl --
# Script to show notes on mathematical subjects
#
# TODO:
# - Implement a number of useful drawing commands
# - Implement a formula renderer (a basic one _is_ available)
# - Implement more convenient bindings
# - Describe the application
#
# Missing commands:
# @refresh - define your own refresh method
# @label - allow a label (useful for variable text)
# @button - allow a pushbutton
#
package require Tcl 8.5
package require Tk
if { [tk windowingsystem] == "x11" } {
. configure -background #dcdad5
option add *background #dcdad5
option add *foreground black
option add *borderWidth 1 widgetDefault
option add *activeBorderWidth 1 widgetDefault
option add *selectBorderWidth 1 widgetDefault
option add *font -adobe-helvetica-medium-r-normal-*-12-*-*-*-*-*-*
option add *padX 2
option add *padY 4
option add *Listbox.background white
option add *Listbox.selectBorderWidth 0
option add *Listbox.selectForeground white
option add *Listbox.selectBackground #4a6984
option add *Entry.foreground black
option add *Entry.background white
option add *Entry.selectBorderWidth 0
option add *Entry.selectForeground white
option add *Entry.selectBackground #4a6984
option add *Text.background white
option add *Text.selectBorderWidth 0
option add *Text.selectForeground white
option add *Text.selectBackground #4a6984
option add *Menu.activeBackground #4a6984
option add *Menu.activeForeground white
option add *Menu.activeBorderWidth 0
option add *Menu.highlightThickness 0
option add *Menu.borderWidth 2
option add *MenuButton.activeBackground #4a6984
option add *MenuButton.activeForeground white
option add *MenuButton.activeBorderWidth 0
option add *MenuButton.highlightThickness 0
option add *MenuButton.borderWidth 0
option add *highlightThickness 0
option add *troughColor #bdb6ad
}
# MathData --
# Namespace for the user-defined commands and data
#
namespace eval ::MathData:: {
variable CNV ""
variable TXT ""
variable fontNormal "Courier 10"
variable fontBold "Courier 10 bold"
variable fontItalic "Courier 10 italic"
}
# scale --
# Set up the scaling for the given canvas
# Arguments:
# data List of data (x, y, x, y ...)
# Result:
# None
# Side effects:
# Scaling parameters set
# Note:
# TODO: Should make sure there is some scaling involved
# if only using pixels
#
proc ::MathData::scale { data } {
variable CNV
variable SCALE
set width [$CNV cget -width]
set height [$CNV cget -height]
set xmin 1.0e30
set xmax -1.0e30
set ymin 1.0e30
set ymax -1.0e30
foreach {x y} $data {
if { $x < $xmin } { set xmin $x }
if { $x > $xmax } { set xmax $x }
if { $y < $ymin } { set ymin $y }
if { $y > $ymax } { set ymax $y }
}
if { $xmin == $xmax } { set xmax [expr {$xmax+1.0}] }
if { $ymin == $ymax } { set ymax [expr {$ymax+1.0}] }
set SCALE(xscale) [expr {$width/double($xmax-$xmin)}]
set SCALE(yscale) [expr {$height/double($ymax-$ymin)}]
set SCALE(xmin) $xmin
set SCALE(xmax) $xmax
set SCALE(ymin) $ymin
set SCALE(ymax) $ymax
}
# polyline --
# Draw a line consisting of multiple points
# Arguments:
# data List of data (x, y, x, y ...)
# colour Colour to use (default: black)
# Result:
# Canvas ID of the polyline
# Side effects:
# Line drawn according to current scales
#
proc ::MathData::polyline { data {colour black} } {
variable CNV
variable SCALE
set xscale $SCALE(xscale)
set yscale $SCALE(yscale)
set xmin $SCALE(xmin)
set xmax $SCALE(xmax)
set ymin $SCALE(ymin)
set ymax $SCALE(ymax)
set pixels {}
foreach {x y} $data {
set px [expr {$xscale*($x-$xmin)}]
set py [expr {$yscale*($ymax-$y)}]
lappend pixels $px $py
}
$CNV create line $pixels -fill $colour
}
# circle --
# Draw a circle with given centre and radius
# Arguments:
# xcentre X-coordinate of the centre
# ycentre Y-coordinate of the centre
# radius Radius of circle
# colour Colour to use (default: black)
# filled Filled or not (default: not)
# Result:
# Canvas ID of the circle
# Side effects:
# Line drawn according to current scales
#
proc ::MathData::circle { xcentre ycentre radius {colour black} {filled 0} } {
variable CNV
variable SCALE
set xscale $SCALE(xscale)
set yscale $SCALE(yscale)
set xmin $SCALE(xmin)
set xmax $SCALE(xmax)
set ymin $SCALE(ymin)
set ymax $SCALE(ymax)
set pixels {}
foreach {x y} [list [expr {$xcentre-$radius}] [expr {$ycentre-$radius}] \
[expr {$xcentre+$radius}] [expr {$ycentre+$radius}] ] {
set px [expr {$xscale*($x-$xmin)}]
set py [expr {$yscale*($ymax-$y)}]
lappend pixels $px $py
}
$CNV create oval $pixels -fill [expr {$filled? $colour : {}}] -outline $colour
}
# point --
# Draw a point with given coordinates
# Arguments:
# xpoint X-coordinate
# ypoint Y-coordinate
# colour Colour to use (default: black)
# Result:
# Canvas ID of the point
# Side effects:
# Line drawn according to current scales
#
proc ::MathData::point { xpoint ypoint {colour black} } {
variable CNV
variable SCALE
set xscale $SCALE(xscale)
set yscale $SCALE(yscale)
set xmin $SCALE(xmin)
set xmax $SCALE(xmax)
set ymin $SCALE(ymin)
set ymax $SCALE(ymax)
set pixels {}
set px [expr {$xscale*($xpoint-$xmin)}]
set py [expr {$yscale*($ymax-$ypoint)}]
lappend pixels [expr {$px-1}] [expr {$py-1}] [expr {$px+1}] [expr {$py+1}]
$CNV create rectangle $pixels -fill $colour -outline $colour
}
# text --
# Draw a text string at a given position
# Arguments:
# x X coordinate
# y Y coordinate
# string String to show
# Result:
# Canvas ID of the text object
# Side effects:
# String drawn
#
proc ::MathData::text { x y string } {
variable CNV
variable SCALE
set xscale $SCALE(xscale)
set yscale $SCALE(yscale)
set xmin $SCALE(xmin)
set xmax $SCALE(xmax)
set ymin $SCALE(ymin)
set ymax $SCALE(ymax)
set px [expr {$xscale*($x-$xmin)}]
set py [expr {$yscale*($ymax-$y)}]
$CNV create text $px $py -text $string -anchor nw
}
# axes --
# Draw two lines representing the axes
# Arguments:
# None
# Result:
# None
# Side effects:
# Two lines drawn (no labels yet)
#
proc ::MathData::axes { } {
variable CNV
variable SCALE
set width [$CNV cget -width]
set height [$CNV cget -height]
set xscale $SCALE(xscale)
set yscale $SCALE(yscale)
set xmin $SCALE(xmin)
set xmax $SCALE(xmax)
set ymin $SCALE(ymin)
set ymax $SCALE(ymax)
set px0 [expr {$xscale*(0.0-$xmin)}]
set py0 [expr {$yscale*($ymax-0.0)}]
$CNV create line $px0 0 $px0 $height -fill black
$CNV create line 0 $py0 $width $py0 -fill black
}
# func --
# Repeatedly run a function and return xy-pairs
# Arguments:
# funcname Name of the function (procedure)
# xmin Minimum x-value
# xmax Maximum x-value
# nosteps Number of steps (inbetween; default: 50)
# Result:
# List of x, y values
#
proc ::MathData::func { funcname xmin xmax { nosteps 50 } } {
set coords {}
set xstep [expr {($xmax-$xmin)/$nosteps}]
for { set i 0 } { $i <= $nosteps } { incr i } {
set x [expr {$xmin+$i*$xstep}]
set y [$funcname $x]
lappend coords $x $y
}
return $coords
}
# circleIntersection --
# Determine the points where two circles intersect
# Arguments:
# circle1 X-, Y-coordinate and radius of first circle
# circle2 X-, Y-coordinate and radius of second circle
# Result:
# List of x/y coordinates or empty
#
proc ::MathData::circleIntersection { circle1 circle2 } {
set coords {}
foreach {x1 y1 r1} $circle1 {break}
foreach {x2 y2 r2} $circle2 {break}
#
# Do we have an intersection?
#
set distc [expr {sqrt(($x1-$x2)**2 + ($y1-$y2)**2)}]
if { $distc**2 <= $r1**2+$r2**2 } {
set a [expr {0.5 * ($distc + ($r1**2-$r2**2)/$distc)}]
set dist [expr {sqrt($r1**2 - $a**2)}]
set dx [expr {$x2-$x1}]
set dy [expr {$y2-$y1}]
set dd [expr {hypot($dx,$dy)}]
set xc [expr {$x1 + $a * $dx/$dd}]
set yc [expr {$y1 + $a * $dy/$dd}]
set xn [expr {$dist * $dy/$dd}]
set yn [expr {-$dist * $dx/$dd}]
set xp1 [expr {$xc + $xn}]
set xp2 [expr {$xc - $xn}]
set yp1 [expr {$yc + $yn}]
set yp2 [expr {$yc - $yn}]
set coords [list $xp1 $yp1 $xp2 $yp2]
}
return $coords
}
# infiniteLine --
# Draw a line through two points that extends indefinitely
# Arguments:
# xp1 X-coordinate first point
# yp1 Y-coordinate first point
# xp2 X-coordinate second point
# yp2 Y-coordinate second point
# colour Colour of the line (default: black)
# Result:
# Canvas ID of the line
#
proc ::MathData::infiniteLine { xp1 yp1 xp2 yp2 {colour black} } {
variable CNV
variable SCALE
set dx [expr {$xp2-$xp1}]
set dy [expr {$yp2-$yp1}]
if { abs($dx) > abs($dy) } {
set xn1 $SCALE(xmin)
set lambda [expr {($xn1 - $xp1) / $dx}]
set yn1 [expr {$yp1 + $dy * $lambda}]
set xn2 $SCALE(xmax)
set lambda [expr {($xn2 - $xp1) / $dx}]
set yn2 [expr {$yp1 + $dy * $lambda}]
} else {
set yn1 $SCALE(ymin)
set lambda [expr {($yn1 - $yp1) / $dy}]
set xn1 [expr {$xp1 + $dx * $lambda}]
set yn2 $SCALE(ymax)
set lambda [expr {($yn2 - $yp1) / $dy}]
set xn2 [expr {$xp1 + $dx * $lambda}]
}
polyline [list $xn1 $yn1 $xn2 $yn2] $colour
}
# MathBook --
# Namespace for the mathbook commands and data
#
namespace eval ::MathBook:: {
variable count 0
variable CNV
variable CNVRIGHT ""
variable CNVCODE
variable TXT
variable REFRESH
}
# @init --
# Execute code once (when reading the notebook file)
# Arguments:
# code Code to run
# Result:
# Nothing
#
proc ::MathBook::@init { code } {
namespace eval ::MathData $code
}
# @canvas --
# Create a canvas of given size
# Arguments:
# width Width in pixels
# height Height in pixels
# code Code to execute
# Result:
# Nothing
# Side effect:
# Canvas created
#
proc ::MathBook::@canvas { width height code } {
variable CNV
variable CNVCODE
variable TXT
variable count
incr count
set CNV $TXT.cnv$count
set ::MathData::CNV $CNV
set CNVCODE($CNV) $code
canvas $CNV -width $width -height $height -bg white
$TXT insert end "\n"
$TXT window create end -window $CNV
$TXT insert end "\n"
namespace eval ::MathData $code
}
# @canvasright --
# Create a canvas of given size on the right of the text
# Arguments:
# width Width in pixels
# height Height in pixels
# code Code to execute
# Result:
# Nothing
# Side effect:
# Canvas created
#
proc ::MathBook::@canvasright { width height code } {
variable CNV
variable CNVCODE
variable CNVRIGHT
if { $CNVRIGHT eq "" } {
set CNVRIGHT [canvas .cnv -width $width -height $height -bg white]
grid configure $CNVRIGHT -row 0 -column 2
} else {
$CNVRIGHT configure -width $width -height $height
}
set CNV $CNVRIGHT
set ::MathData::CNV $CNV
namespace eval ::MathData $code
}
# @entry --
# Create an entry widget of given width
# Arguments:
# name Name of the associated variable
# width Width of the widget (in characters)
# Result:
# Nothing
# Side effect:
# Entry created
#
proc ::MathBook::@entry { name width } {
variable TXT
variable count
incr count
set entry $TXT.entry$count
entry $entry -textvariable ::MathData::$name -width $width
$TXT window create end -window $entry
bind $entry <Return> ::MathBook::Refresh
}
# @label --
# Create a label widget of given width
# Arguments:
# name Name of the associated variable
# width Width of the widget (in characters)
# Result:
# Nothing
# Side effect:
# Label created
#
proc ::MathBook::@label { name width } {
variable TXT
variable count
incr count
set label $TXT.label$count
label $label -textvariable ::MathData::$name -width $width \
-background white -anchor nw -font $::MathData::fontNormal
$TXT window create end -window $label
}
# @button --
# Create a pushbutton
# Arguments:
# label Label for the pushbutton
# code Code to apply
# Result:
# Nothing
# Side effect:
# Button created
#
proc ::MathBook::@button { label code } {
variable TXT
variable count
variable buttoncount
incr buttoncount
set button .buttons.button$buttoncount
button $button -text $label -command "namespace eval ::MathData [list $code]" -width 10
grid configure $button -column $buttoncount -row 0
}
# @refresh --
# Define a refresh method - called before the canvas methods
# Arguments:
# code Code to be run on refresh
# Result:
# None
# Side effect:
# Defines the REFRESH variable
#
proc ::MathBook::@refresh { code } {
variable REFRESH
set REFRESH $code
}
# Refresh --
# Refresh the canvases and labels etc.
# Arguments:
# None
# Result:
# None
# Side effect:
# Canvases refreshed and whatever occurs in the @refresh method
#
proc ::MathBook::Refresh { } {
variable CNV
variable CNVCODE
variable REFRESH
variable TXT
variable count
if { [info exists REFRESH] } {
namespace eval ::MathData $REFRESH
}
foreach {name code} [array get CNVCODE] {
set ::MathData::CNV $name
$name delete all
namespace eval ::MathData $code
}
}
# initMainWindow --
# Create the main window
# Arguments:
# None
# Result:
# None
# Side effect:
# Main window created
#
proc ::MathBook::initMainWindow { } {
variable TXT
variable count
set count 0
set buttoncount -1
set menu [menu .mb -type menubar]
. configure -menu .mb
.mb add cascade -label File -underline 0 -menu .mb.file
menu .mb.file -tearoff 0
.mb.file add command -label Open -command ::MathBook::OpenTextFile
.mb.file add command -label Exit -command exit
set tf .textframe
set tw $tf.text
set buttons .buttons
set TXT $tw
set ::MathData::TXT $tw
frame $tf
scrollbar $tf.scrollx -orient horiz -command "$tw xview"
scrollbar $tf.scrolly -command "$tw yview"
text $tw -yscrollcommand "$tf.scrolly set" \
-xscrollcommand "$tf.scrollx set" \
-fg black -bg white -font "courier 10" \
-wrap word
grid $tw $tf.scrolly
grid $tf.scrollx x
grid $tw -sticky news
grid $tf.scrolly -sticky ns
grid $tf.scrollx -sticky ew
grid columnconfigure $tf 0 -weight 1
grid rowconfigure $tf 0 -weight 1
frame $buttons
button $buttons.refresh -text Refresh -command ::MathBook::Refresh -width 10
grid $tf - -sticky news
grid $buttons.refresh -sticky news
grid $buttons -sticky news
grid columnconfigure . 0 -weight 1
grid columnconfigure . 1 -weight 1
grid rowconfigure . 0 -weight 1
$tw tag configure bigbold -font "helvetica 12 bold"
$tw tag configure normal -font "courier 10"
$tw tag configure preform -font "courier 10" -background "lightgrey"
$tw tag configure indent -lmargin2 16
}
# fillTextWindow --
# Fill the text window
# Arguments:
# filename Name of the notebook file to use
# Result:
# None
# Side effect:
# Text window filled
#
proc ::MathBook::fillTextWindow { filename } {
variable TXT
set infile [open $filename "r"]
set just ""
set indent ""
set tag normal
while { [gets $infile line] >= 0 } {
set trimmed [string trim $line]
#
# Analyse the contents ...
#
if { [string first "#" $trimmed] == 0 } {
continue
}
# Ignore empty lines, unless in preformatted text
if { $trimmed == "" } {
if { $just != "" } {
$TXT insert end "\n" $tag
}
continue
}
if { [string first "@" $trimmed] == 0 } {
RunWholeCommand $infile $line
continue
}
if { [string first "<h1>" $trimmed] == 0 } {
set tag bigbold
set trimmed [string map {<h1> "" </h1> ""} $trimmed]
}
if { [string first "<b>" $trimmed] == 0 } {
set tag [list bold $indent]
set trimmed [string map {<b> "" </b> ""} $trimmed]
}
if { [string first "<i>" $trimmed] == 0 } {
set tag [list italic $indent]
set trimmed [string map {<i> "" </i> ""} $trimmed]
}
if { [string first "<pre>" $trimmed] == 0 } {
$TXT insert end "\n"
set tag "preform"
set just "\n"
continue
}
if { [string first "</pre>" $trimmed] == 0 } {
$TXT insert end "\n"
set tag [list normal $indent]
set just ""
continue
}
if { [string first "<p>" $trimmed] == 0 } {
$TXT insert end "\n\n"
continue
}
if { [string first "<br>" $trimmed] == 0 } {
$TXT insert end "\n"
continue
}
if { [string first "<ul>" $trimmed] == 0 } {
set indent "indent"
continue
}
if { [string first "</ul>" $trimmed] == 0 } {
$TXT insert end "\n\n"
set indent ""
continue
}
if { [string first "<li>" $trimmed] == 0 } {
$TXT insert end "\n* " indent
continue
}
if { [string first "<tag>" $trimmed] == 0 } {
set tag [list [lindex $tag 0] $indent [string trim [string range $trimmed 5 end]]]
continue
}
if { $just == "" } {
$TXT insert end "$trimmed " $tag
} else {
$TXT insert end "$line\n" $tag
}
if { $tag == "bigbold" || $tag == "italic" || $tag == "bold" } {
set tag "normal"
}
}
close $infile
$TXT configure -state disabled
wm title . "$filename - MathBook"
}
# OpenTextFile --
# Select a text file and display the contents
# Arguments:
# None
# Result:
# None
# Side effect:
# The contents is shown
#
proc ::MathBook::OpenTextFile {} {
variable TXT
variable CNVRIGHT
set types {
{{Text Files} {*.txt}}
{{All Files} *}
}
set filename [tk_getOpenFile -filetypes $types -parent . -title "Select mathbook file"]
if { $filename != "" } {
$TXT delete 1.0 end
if { $CNVRIGHT != "" } {
destroy $CNVRIGHT
}
fillTextWindow $filename
}
}
# RunWholeCommand --
# Run an embedded command
# Arguments:
# infile Handle to the file
# line First line of the command
# Result:
# None
# Side effect:
# Whatever the command does
#
proc ::MathBook::RunWholeCommand { infile line } {
variable TXT
while { ! [info complete $line] } {
if { [gets $infile nextline] >= 0 } {
append line "\n$nextline"
} else {
break
}
}
eval $line
}
# main --
# Get the whole thing going
#
::MathBook::initMainWindow
if { [llength $argv] > 0 } {
::MathBook::fillTextWindow [lindex $argv 0]
} else {
$::MathBook::TXT insert end "-- please open a mathbook file --"
wm title . "MathBook"
}JM I may have done something wrong, but I am getting this error when opening the sample file...
wrong # args: should be "linsert list index element ?element ...?"
wrong # args: should be "linsert list index element ?element ...?"
while executing
"linsert $tag 0"
(procedure "fillTextWindow" line 81)
invoked from within
"fillTextWindow $filename"
(procedure "::MathBook::OpenTextFile" line 13)
invoked from within
"::MathBook::OpenTextFile"arjen - 2011-12-20 03:11:03
The linsert should be an lindex and the given example does not load properly (i.e. no canvas on the right) so that is something I have to look into. Try:
wish mathbook.tcl tangentcircle.txt
instead. I have _not_ seen the error message you report though. Are you using Tcl/Tk 8.4 or 8.5/8.6?
arjen - 2011-12-20 03:13:53
Just answered my own question: you get this error with Tcl/Tk 8.4. I will have corrected this. I will look at the other problem later.
-- Deletion should be done in the right order - that was the mistake. Corrected.
Jorge - 2011-12-20 22:28:13
correct, I just change to 8.6 and works fine (it still throws error messages with 8.4, ** vs pow for example). Thanks, nice job.
arjen - 2011-12-21 02:58:12
Thanks for the compliment. As for ** versus pow(), I have to add a package require Tcl 8.5 in there.