#!/usr/bin/env wish
#---- Auxilary procedures --------------------------------
proc framed-label-and-entry {parent name text} {
# packs a label and a text entry into a frame
set fullname .$parent.$name
pack [frame $fullname] -side top -fill x
label $fullname.l -width 15 -text $text -padx 0 -anchor w
entry $fullname.e -width 15 -relief sunken
pack $fullname.l .$parent.$name.e -side left
}
proc make-keys {} {
# make command arguments for mrunner.lua
set y {}
global entry_options
foreach opt $entry_options {
global [lindex $opt 0]
set optname [lindex $opt 0]
if {[lindex $opt 3] != 0 && \
[set $optname] != {}} {
set optname [lindex $opt 0]
lappend y [lindex $opt 3] [set $optname]
}
}
lappend y -e
lappend y [stims->lua]
return $y
}
# Run simulation
proc Run {} {
global input runbut out-file modeltime output
global command xdata compartments yvectors
upvar layout lo
set dictfile $lo-dict.gp
xdata delete :
foreach name [array names compartments -glob "*.*"] {
global y_${name}
y_$name delete :
}
set keys [make-keys]
puts "$command mrunner.lua $keys > ${out-file}"
#set input [open "| $command mrunner.lua $keys "]
if [catch {open "| $command mrunner.lua $keys "} input] {
bgerror "Couldn't start lua process"
} else {
set output [open ${out-file} w]
fconfigure $input -blocking 0; # needed for win32
$runbut config -text "Stop" -command Stop \
-activebackground red \
-background pink
fileevent $input readable Log
if { [catch {open $dictfile r} dict]} {
after 15000 set dict fail
while 1 {
after 20
if [file exists $dictfile] {
set dict [open $dictfile]
break
}
}
vwait dict
}
if {$dict != "fail"} {
read-gp-dict $dict compartments
close $dict
.controls.addplot config -command \
{add-plot-window compartments plot_windows}
setup-yvectors yvectors compartments
.controls.addplot config -state active
}
}
}
# log model time from the output file
proc Log {} {
global compartments
global input modeltime output
global xdata yvectors
global max-points
if [eof $input] { Stop
} else {
gets $input line
if {[string length $line] > 0} {
puts $output $line
set modeltime [format "%4.3f" [lindex $line 0]]
set dlength [xdata length]
if {$dlength>${max-points}} {
xdata delete 0
}
set xdata(++end) $modeltime
foreach name [array names compartments -glob "*.*"] {
# this must be slow: random acces on a list
# todo: think of better line parsing
global y_${name}
if {$dlength>${max-points}} {
y_${name} delete 0
}
set y_${name}(++end) [lindex $line [set yvectors(${name})]]
}
}
}
}
# Stop the running lua/luajit process
proc Stop {} {
global runbut input output
global xdata ydata
global tcl_platform
if [regexp -nocase {win} [set tcl_platform(platform)]] {
catch {exec tskill [pid $input]}
}
catch {close $input}
catch {close $output}
$runbut config -text "Run" -command Run \
-background lightgreen -activebackground green
}
# Delete stimulation row
proc remove-stim {id} {
global stimuli stimulation_params
destroy $stimuli($id,frame)
foreach p $stimulation_params {
unset stimuli($id,$p)
}
unset stimuli($id,frame)
}
# Add new stimulation row
proc add-stim {} {
global stimuli stimulation_params
incr stimuli(id)
set id $stimuli(id)
pack [set stimuli($id,frame) [frame .stimuli.body.$id]]
foreach p $stimulation_params {
pack [entry .stimuli.body.$id.$p \
-textvariable stimuli($id,$p) \
-width 5] -side left
}
pack [button .stimuli.body.$id.del -text "x"\
-command "remove-stim $id"] -side left
}
proc stims->lua {} {
global stimuli stimulation_params
set res "return "
append res "{"
for {set id 0} {$id <= $stimuli(id)} {incr id} {
set stim [array get stimuli $id*]
if {$stim != {} } {
set st {}
append st "{"
foreach p $stimulation_params {
append st "$p=$stimuli($id,$p), "
}
append st "}, "
append res $st
}
}
append res "} "
}
proc read-gp-dict {fobj compartments} {
# reads gnuplot dictionary
upvar $compartments comps
set comp_prev ""
set comps(names) [list]
while {[gets $fobj line] >= 0} {
if [regexp {([a-z]+[0-9]+)_([a-z]+[0-9]+)_(.+) = ([0-9]+)} \
$line all tag comp var id] {
if {$comp != $comp_prev} {
lappend comps(names) $comp
}
set comp_prev $comp
set comps($comp.$var) \
[list $var [expr $id - 1]]
}
}
}
proc make-var-menu {id compartments} {
upvar $compartments comps
set win .plot-$id
menubutton $win.menubar.pl -text {Variable} \
-menu $win.menubar.pl.m
pack $win.menubar.pl -side left
set m [menu $win.menubar.pl.m]
set i 0
foreach key $comps(names) {
$m add cascade -label $key -menu $m.sub$i
set sub [menu $m.sub$i]
incr i
foreach name [lsort [array names comps $key.*]] {
$sub add checkbutton -label [lindex [set comps($name)] 0] \
-variable varflags($id-$name) \
-command "add/remove-element $id $name ng"
}
}
}
proc make-save-graph-menu {id graph} {
set win .plot-$id
menubutton $win.menubar.sv -text {Save} \
-menu $win.menubar.sv.m
pack $win.menubar.sv -side left
set m [menu $win.menubar.sv.m]
$m add command -label {Eps} -command "save-eps $graph"
$m add command -label {Bitmap} -command "save-bitmap $graph"
}
proc save-eps {graph} {
set savename [tk_getSaveFile \
-defaultextension .eps \
-filetypes {
{{Encapsulated postscript} {.eps}}
{{All files} * }
}]
if {$savename != ""} {
$graph postscript configure \
-landscape yes -center yes -maxpect yes \
-decorations no
$graph postscript output $savename
}
}
proc save-bitmap {graph} {
$graph snap -format EMF CLIPBOARD
}
proc UnZoom {graph} {
$graph axis configure x y -min {} -max {}
}
proc Zoom {graph x1 y1 x2 y2 } {
if {$x1 > $x2} {
$graph axis configure x -min $x2 -max $x1
} elseif {$x2 > $x1} {
$graph axis configure x -min $x1 -max $x2
}
if {$y1 > $y2} {
$graph axis configure y -min $y2 -max $y1
} elseif {$y2 > $y1} {
$graph axis configure y -min $y1 -max $y2
}
}
proc RegionStart {graph x y} {
global x0 y0
set x [$graph axis invtransform x $x]
set y [$graph axis invtransform y $y]
$graph marker create line \
-coords "" \
-name regionline \
-dashes dash -xor yes
set x0 $x
set y0 $y
}
proc slide-start {graph x y} {
global x0 y0
set x [$graph axis invtransform x $x]
set y [$graph axis invtransform y $y]
set x0 $x
set y0 $y
}
proc slide-move {graph x y} {
global x0 y0
set x [$graph axis invtransform x $x]
set y [$graph axis invtransform y $y]
set xmin [$graph axis cget x -min]
set xmax [$graph axis cget x -max]
set ymin [$graph axis cget y -min]
set ymax [$graph axis cget y -max]
set xdelta [expr $x0 - $x]
set ydelta [expr $y0 - $y]
if {$xmin != ""} {
set xmin [expr $xmin + $xdelta]
set xmax [expr $xmax + $xdelta]
set ymin [expr $ymin + $ydelta]
set ymax [expr $ymax + $ydelta]
$graph axis configure x -min $xmin -max $xmax
$graph axis configure y -min $ymin -max $ymax
}
}
proc RegionMotion {graph x y} {
global x0 y0
set x [$graph axis invtransform x $x]
set y [$graph axis invtransform y $y]
$graph marker configure regionline \
-coords "$x0 $y0 $x0 $y $x $y $x $y0 $x0 $y0" \
}
proc RegionEnd {graph x y} {
global x0 y0
$graph marker delete regionline
set x [$graph axis invtransform x $x]
set y [$graph axis invtransform y $y]
Zoom $graph $x0 $y0 $x $y
}
proc update-graph-xy0 {graph x y} {
set x [$graph axis invtransform x $x]
set y [$graph axis invtransform y $y]
upvar graphxy gxy
set gxy [format "%4.3f,%4.3f" $x $y]
}
proc update-graph-xy {graph x y label} {
set x [$graph axis invtransform x $x]
set y [$graph axis invtransform y $y]
set gxy [format "%4.3f,%4.3f" $x $y]
$label configure -text $gxy
}
set colors {black blue green red magenta yellow}
proc last-dir {} {
lindex [split [pwd] / ] end
}
proc add-plot-window {compartments plot_windows} {
global varflags graphs_count
upvar $plot_windows plots
upvar $compartments comps
blt::vector create ydata
incr plots(id)
set id $plots(id)
set win [toplevel .plot-$id -width 600 -height 600]
wm title $win "[last-dir]: plot-$id"
set graphs_count($id) 0
pack [frame $win.menubar] -fill x
make-var-menu $id comps
set graph [blt::graph .plot-$id.g -width 400 -height 260]
pack $graph -fill both
$graph grid on
bind $graph <ButtonPress-1> {slide-start %W %x %y}
bind $graph <B1-Motion> {slide-move %W %x %y}
bind $graph <ButtonPress-3> {RegionStart %W %x %y}
bind $graph <B3-Motion> {RegionMotion %W %x %y}
bind $graph <ButtonRelease-3> {RegionEnd %W %x %y}
bind $graph <ButtonRelease-2> {UnZoom %W}
make-save-graph-menu $id $graph
bind $win <g> "$graph grid toggle"
#bind $win <l> "$graph legend toggle"
bind $win <a> "UnZoom $graph"
set graphxy [label $win.menubar.graphxy \
-relief sunken \
-text "undefined"]
pack $graphxy -side right
bind $graph <Motion> \
"update-graph-xy %W %x %y $graphxy"
return $graph
}
proc setup-yvectors {yvectors compartments} {
#ODO: colt blt vectors into an array
upvar $yvectors yvs $compartments comps
foreach name [array names comps -glob "*.*"] {
set yvs($name) [lindex [set comps($name)] 1]
global y_$name
if {![info exists y_$name]} {
blt::vector create y_${name}}
}
}
proc add/remove-element {plotid name ngraphs} {
global xdata varflags yvectors graphs_count
global colors
set ng [set graphs_count($plotid)]
foreach n [array names comps -glob "*.*"] {
global y_${n}
}
if $varflags($plotid-$name) {
.plot-$plotid.g element create $name -symbol "" \
-color [lindex $colors [expr $ng % [llength $colors]]] \
-xdata xdata -ydata y_$name
incr graphs_count($plotid)
} else {
#incr graphs_count($plotid) -1
.plot-$plotid.g element delete $name
}
}
#---------------------------------------------------------
### Main program starts here ###
#package require platform
package require BLT
# Set window title
set model_suffix [lindex $argv 0]
wm title . "MGUI-$model_suffix: [last-dir]"
# Container frame for run parameters
labelframe .options -text "Parameters"
# Container frame for run controls
frame .controls -borderwidth 5
# Container frame for stimulation protocols
labelframe .stimuli -text "Stimulation"
# Container frame for time logging
labelframe .log -text "Log"
pack .options .stimuli .log .controls -side top -fill x
# Control buttons
button .controls.quit -text "Quit" -command exit
set plot_windows(id) -1
button .controls.addplot -text "Add plot"
global xdata
blt::vector create xdata; # this one will be shared
#if {![catch {open } dictfile]} {
# read-gp-dict $dictfile compartments
# .controls.addplot config -command {add-plot-window \
# compartments plot_windows}
# setup-yvectors yvectors compartments##
#
#} else {
# .controls.addplot config -state disabled
#}
.controls.addplot config -state disabled
set runbut [button .controls.run -text "Run" \
-background lightgreen -activebackground green \
-command Run]
pack .controls.run .controls.addplot .controls.quit -side left
## Option entries ##
set model_suffix [lindex $argv 0]
set entry_options [list \
{start-time "Start time:" 0 -t} \
{stop-time "Stop time:" 1000 -T} \
{stepper "Stepper func:" rkc_a -i}\
{init-h "Init. time step:" 1e-3 -h}\
{rkc-s "RKC dimension:" 200 -s} \
{config "Config file:" dc-conf -c} \
{model "Model file:" dc-mod -m} \
"layout {Nerve layout} $model_suffix -L"\
{load-file "Load state from:" "" -l}\
"{save-file} {Save state to:} $model_suffix-state.lua -S"\
{save-period "Save period:" 1e4 -w}\
{max-points "Max pts/graph:" 5e4 0}\
{out-file "Output file:" out.0 0}\
{command "Run with:" "lua" 0}
]
# Create entries for the available options
foreach option $entry_options {
set optname [lindex $option 0]
set $optname [lindex $option 2]
framed-label-and-entry options $optname [lindex $option 1]
.options.$optname.e config -textvariable $optname
}
if [regexp -nocase {win} [set tcl_platform(platform)]] {
set command lua
} else {set command "lua"}
## Stimulation ##
set stimulation_params {amp start stop int width}
pack [frame .stimuli.head] [frame .stimuli.body] -side top -anchor w
foreach p $stimulation_params {
pack [label .stimuli.head.$p -text $p -width 5] -side left
}
set stimuli(id) -1
set addstim [button .stimuli.head.add -text "+" -command add-stim]
pack $addstim -side left
## A bit of logging ##
set modeltime 0
label .log.lab -text "Model time:"
label .log.time -width 15 -relief sunken -textvariable modeltime
pack .log.lab .log.time -side left