Minsky
minsky.tcl
Go to the documentation of this file.
1 # @copyright Steve Keen 2012
2 # @author Russell Standish
3 # This file is part of Minsky.
4 #
5 # Minsky is free software: you can redistribute it and/or modify it
6 # under the terms of the GNU General Public License as published by
7 # the Free Software Foundation, either version 3 of the License, or
8 # (at your option) any later version.
9 #
10 # Minsky is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with Minsky. If not, see <http://www.gnu.org/licenses/>.
17 #
18 
19 
20 set fname ""
21 set workDir [pwd]
22 encoding system utf-8
23 
24 # On mac-build versions, fontconfig needs to find its config file,
25 # which is packaged up in the Minsky.app directory
26 
27 if {$tcl_platform(os)=="Darwin" && [file exists $minskyHome/../Resources/fontconfig/fonts.conf]} {
28  set env(FONTCONFIG_FILE) $minskyHome/../Resources/fontconfig/fonts.conf
29 }
30 
31 if {[minsky.ravelVersion]=="unavailable"} {
32  set progName Minsky
33 } else {
34  set progName Ravel
35 }
36 
37 # default canvas size. Overridden by previously resized window size
38 # saved in .minskyrc
39 set canvasWidth 600
40 set canvasHeight 800
41 set backgroundColour lightGray
42 set preferences(nRecentFiles) 10
43 set preferences(panopticon) 0
44 set preferences(focusFollowsMouse) 0
45 set preferences(multipleEquities) 0
46 set recentFiles {}
47 
48 # select Arial Unicode MS by default, as this gives decent Unicode support
49 switch $tcl_platform(os) {
50  "Darwin" -
51  "windows" {minsky.defaultFont "Arial Unicode MS"}
52 }
53 
54 # read in .rc file, which differs on Windows and unix
55 set rcfile ""
56 if {$tcl_platform(platform)=="unix"} {
57  set rcfile "$env(HOME)/.minskyrc"
58 } elseif {$tcl_platform(platform)=="windows"} {
59  set rcfile "$env(USERPROFILE)/.minskyrc"
60 }
61 if [file exists $rcfile] {
62  catch {source $rcfile}
63 }
64 
65 # for some reason (MingW I'm looking at you), tcl_library sometimes
66 # points to Minsky's library directory
67 if {![file exists [file join $tcl_library init.tcl]] ||
68  [file normalize $tcl_library]==[file normalize $minskyHome/library]} {
69  set tcl_library $minskyHome/library/tcl
70 }
71 
72 
73 # hopefully, we can now rely on TCL/Tk to find its own libraries.
74 if {![info exists tk_library]} {
75  regsub tcl [file tail [info library]] tk tk_library
76  set tk_library [file join [file dirname [info library]] $tk_library]
77 }
78 if {![file exists [file join $tk_library tk.tcl]]
79 } {
80  set tk_library $minskyHome/library/tk
81 }
82 
83 # In a rather bizarre set of events, namely Aqua build, launched via
84 # launch services, on a non-developer machine, Tk will instantiate a
85 # second TCL interpreter to host a console. Unfortunately, it uses the
86 # original heuristics to find the TCL and TK libraries, which fail,
87 # causing the application to hang. Setting these environment variables
88 # cuts through that problem. See ticket #675.
89 set env(TCL_LIBRARY) $tcl_library
90 set env(TK_LIBRARY) $tk_library
91 
92 proc setFname {name} {
93  global fname workDir progName
94  if [string length $name] {
95  set fname $name
96  set workDir [file dirname $name]
97  catch {wm title . "$progName: $fname"}
98  }
99 }
100 
101 # needed for scripts/tests
102 rename exit tcl_exit
103 
104 # emulate minsky.value.value's default argument
105 proc value.value {args} {
106  if [llength $args] {
107  return [minsky.value.value [lindex $args 0]]
108  } else {
109  return [minsky.value.value 0]
110  }
111 }
112 proc attachTraceProc {namesp} {
113  foreach p [info commands $namesp*] {
114  if {$p ne "::traceProc"} {
115  trace remove execution $p enterstep traceProc
116  trace add execution $p enterstep traceProc
117  }
118  }
119  # recursively process child namespaces
120  foreach n [namespace children $namesp] {
121  attachTraceProc ${n}::
122  }
123 }
124 
125 proc traceProc {args} {
126  array set frameInfo [info frame -2]
127  if {[info exists frameInfo(proc)]&&[info exists frameInfo(line)]} {
128  cov.add $frameInfo(proc) $frameInfo(line)
129  }
130  if {[info exists frameInfo(file)]&&[info exists frameInfo(line)]} {
131  cov.add $frameInfo(file) $frameInfo(line)
132  }
133 }
134 
135 if [info exists env(MINSKY_COV)] {
136  # open coverage database, and set cache size
137  Coverage cov
138  cov.init $env(MINSKY_COV) w
139  cov.max_elem 10000
140  rename tcl_exit tcl_exit2
141  proc tcl_exit {args} {
142  # disable coverage testing
143  proc traceProc {args} {}
144  cov.close
145  eval tcl_exit2 $args
146  }
147 # attachTraceProc ::
148 }
149 
150 #Needs to be present to allow callbacks to change the cursor for busy operations
151 proc setCursor {cur} {}
152 
153 #if argv(1) has .tcl extension, it is a script, otherwise it is data
154 if {$argc>1 && [string match "*.tcl" $argv(1)]} {source $argv(1)}
155 
156 source $tcl_library/init.tcl
157 
158 if {$tcl_platform(os)=="Darwin"} {
159  if {[catch {GUI}] || [catch {source [file join $tk_library tk.tcl]}]} {
160  # pop a message box about installing XQuartz
161  exec osascript << "tell application \"System Events\"
162  activate
163  display dialog \"GUI failed to initialise, try installing XQuartz\"
164  end tell"
165 }
166 } else {
167  GUI
168  source [file join $tk_library tk.tcl]
169 }
170 
171 source [file join $tk_library bgerror.tcl]
172 source $minskyHome/library/init.tcl
173 source $minskyHome/helpRefDb.tcl
174 
175 disableEventProcessing
176 
177 # Tk's implementation of bgerror does not mark the error dialog as
178 # transient, creating a usability problem where a user could hide the
179 # dialog, and wonder why the application is not responding.
180 rename ::tk::SetFocusGrab ::tk::SetFocusGrab_
181 proc ::tk::SetFocusGrab {grab focus} {
182  ::tk::SetFocusGrab_ $grab $focus
183  wm attributes $grab -topmost 1
184  wm transient $grab .
185 }
186 
187 catch {console hide}
188 
189 proc setBackgroundColour bgc {
190  global backgroundColour
191  set backgroundColour $bgc
192  tk_setPalette $bgc
193  # tk_setPalette doesn't understand ttk widgets
194  ttk::style configure TNotebook -background $backgroundColour
195  ttk::style configure TNotebook.Tab -background $backgroundColour
196  ttk::style map TNotebook.Tab -background "selected $bgc active $bgc"
197  if [winfo exists .controls.runmode] {.controls.runmode configure -selectcolor $bgc}
198 }
199 
200 # disable tear-off menus
201 option add *Menu.tearOff 0
202 wm deiconify .
203 tk appname [file rootname [file tail $argv(0)]]
204 wm title . "$progName: $fname"
205 setBackgroundColour $backgroundColour
206 proc tk_focusPrev {win} {return $win}
207 proc tk_focusNext {win} {return $win}
208 if {$preferences(focusFollowsMouse)} {
209  tk_focusFollowsMouse
210 # Make tab traversal possible within a window that is given focus by only clicking on it (no focusFollowsMouse). For ticket 901.
211 } else {
212  set old [bind all <Enter>]
213  set script {
214  if {"%d" eq "NotifyAncestor" || "%d" eq "NotifyNonlinear" \
215  || "%d" eq "NotifyInferior"} {
216  tk::FocusOK %W
217  }
218  }
219  if {$old ne ""} {
220  bind all <Enter> "$old; $script"
221  } else {
222  bind all <Enter> $script
223  }
224 }
225 proc setCursor {cur} {. configure -cursor $cur; update idletasks}
226 
227 if {[tk windowingsystem]=="win32"} {
228  # redirect the mousewheel event to the actual window that should
229  # receive the event - see ticket #114
230  bind . <MouseWheel> {
231  switch [winfo containing %X %Y] {
232  .wiring.canvas {
233  if {%D>=0} {
234  # on Winblows, min val of |%D| is 120, so just use sign
235  zoomAt %x %y 1.1
236  } {
237  zoomAt %x %y [expr 1.0/1.1]
238  }
239  }
240  }
241  }
242 }
243 
244 source $minskyHome/library/tooltip.tcl
245 namespace import tooltip::tooltip
246 
247 source $minskyHome/library/obj-browser.tcl
248 
249 # Macs have a weird numbering of mouse buttons, so lets virtualise B2 & B3
250 # see http://wiki.tcl.tk/14728
251 if {[tk windowingsystem] == "aqua"} {
252  event add <<contextMenu>> <Button-2> <Control-Button-1>
253  event add <<middleMouse>> <Button-3>
254  event add <<middleMouse-Motion>> <B3-Motion>
255  event add <<middleMouse-ButtonRelease>> <B3-ButtonRelease>
256 } else {
257  event add <<contextMenu>> <Button-3>
258  event add <<middleMouse>> <Button-2>
259  event add <<middleMouse-Motion>> <B2-Motion>
260  event add <<middleMouse-ButtonRelease>> <B2-ButtonRelease>
261 }
262 
263 use_namespace minsky
264 
265 proc deiconify {widget} {
266  wm deiconify $widget
267  after idle "wm deiconify $widget; raise $widget; focus -force $widget"
268 }
269 
270 #labelframe .menubar -relief raised
271 menu .menubar -type menubar
272 
273 if {[tk windowingsystem] == "aqua"} {
274  menu .menubar.apple
275  .menubar.apple add command -label "About Minsky" -command aboutMinsky
276  .menubar add cascade -menu .menubar.apple
277  set meta Command
278  set meta_menu Cmd
279 
280 # handle opening of files once Minsky is in flight
281  proc ::tk::mac::OpenDocument {args} {
282  # only support opening a single document at a time
283  if {[llength $args]>0} {openNamedFile [lindex $args 0]}
284  }
285  proc ::tk::mac::ShowPreferences {} {showPreferences}
286  proc ::tk::mac::ShowHelp {} {help Introduction}
287 } else {
288  # keyboard accelerator introducer, which is different on macs
289  set meta Control
290  set meta_menu Ctrl
291 }
292 
293 menu .menubar.file
294 .menubar add cascade -menu .menubar.file -label File -underline 0
295 
296 menu .menubar.edit -postcommand togglePaste
297 .menubar add cascade -menu .menubar.edit -label Edit -underline 0
298 
299 menu .menubar.bookmarks -postcommand generateBookmarkMenu
300 .menubar add cascade -menu .menubar.bookmarks -label Bookmarks -underline 0
301 menu .menubar.bookmarks.deleteMenu
302 
303 menu .menubar.ops
304 .menubar add cascade -menu .menubar.ops -label Insert -underline 0
305 
306 menu .menubar.options
307 .menubar add cascade -menu .menubar.options -label Options -underline 0
308 .menubar.options add command -label "Preferences" -command showPreferences
309 
310 # add rows to this table to add new preferences
311 # valid types are "text", "bool" and "{ enum label1 val1 label2 val2 ... }"
312 # varName Label DefaultVal type
313 set preferencesVars {
314  godleyDisplay "Godley Table Show Values" 1 bool
315  godleyDisplayStyle "Godley Table Output Style" sign { enum
316  "DR/CR" DRCR
317  "+/-" sign }
318  multipleEquities "Enable multiple equity columns" 1 bool
319  nRecentFiles "Number of recent files to display" 10 text
320  wrapLaTeXLines "Wrap long equations in LaTeX export" 1 bool
321  panopticon "Enable panopticon" 1 bool
322  focusFollowsMouse "Focus follows mouse" 1 bool
323 }
324 lappend preferencesVars defaultFont "Font" [defaultFont] font
325 
326 foreach {var text default type} $preferencesVars {
327  # don't override value set in the rc file
328  if {![info exists preferences($var)]} {
329  set preferences($var) $default
330  }
331 }
332 
333 proc showPreferences {} {
334  if [winfo exists .preferencesForm] return
335  global preferences_input preferences preferencesVars
336  foreach var [array names preferences] {
337  set preferences_input($var) $preferences($var)
338  }
339 
340  toplevel .preferencesForm
341  wm resizable .preferencesForm 0 0
342 
343  set row 0
344 
345  grid [label .preferencesForm.label$row -text "Preferences"] -column 1 -columnspan 999 -pady 10
346  incr row 10
347 
348  # pad the left and right
349  grid [frame .preferencesForm.f1] -column 1 -row 1 -rowspan 999 -padx 10
350  grid [frame .preferencesForm.f2] -column 999 -row 1 -rowspan 999 -padx 10
351 
352 
353  foreach {var text default type} $preferencesVars {
354  set rowdict($text) $row
355 
356  grid [label .preferencesForm.label$row -text $text] -column 10 -row $row -sticky e -pady 5
357 
358  switch $type {
359  text {
360  grid [entry .preferencesForm.text$row -width 20 -textvariable preferences_input($var)] -column 20 -row $row -sticky ew -columnspan 999
361  }
362  bool {
363  grid [checkbutton .preferencesForm.cb$row -variable preferences_input($var)] -row $row -column 20 -sticky w
364  }
365  font {
366  grid [ttk::combobox .preferencesForm.font -textvariable preferences_input($var) -values [lsort [listFonts]] -state readonly] -row $row -column 20 -sticky w
367  image create cairoSurface fontSampler -surface minsky.fontSampler
368  grid [label .preferencesForm.fontSample -image fontSampler -width 150 -height 20] -row $row -column 30 -sticky w
369  bind .preferencesForm.font <<ComboboxSelected>> {
370  defaultFont [.preferencesForm.font get]
371  fontSampler.requestRedraw
372  canvas.requestRedraw
373  }
374  }
375  default {
376  if {[llength $type] > 1} {
377  switch [lindex $type 0] {
378  enum {
379  set column 20
380  foreach {valtext val} [lrange $type 1 end] {
381  grid [radiobutton .preferencesForm.rb${row}v$column -text $valtext -variable preferences_input($var) -value $val] -row $row -column $column
382  incr column
383  }
384  }
385  }
386  } else { error "unknown preferences widget $type"}
387  }
388  }
389 
390  incr row 10
391  }
392 
393  set preferences(initial_focus) ".preferencesForm.cb$rowdict(Godley Table Show Values)"
394 
395  frame .preferencesForm.buttonBar
396  button .preferencesForm.buttonBar.ok -text OK -command {setPreferenceParms; closePreferencesForm; redrawAllGodleyTables}
397  button .preferencesForm.buttonBar.cancel -text cancel -command {closePreferencesForm}
398  pack .preferencesForm.buttonBar.ok [label .preferencesForm.buttonBar.spacer -width 2] .preferencesForm.buttonBar.cancel -side left -pady 10
399  grid .preferencesForm.buttonBar -column 1 -row 999 -columnspan 999
400 
401  bind .preferencesForm <Key-Return> {invokeOKorCancel .preferencesForm.buttonBar}
402 
403  wm title .preferencesForm "Preferences"
404 
405  deiconify .preferencesForm
406  update idletasks
407  ::tk::TabToWindow $preferences(initial_focus)
408  ensureWindowVisible .preferencesForm
409  grab set .preferencesForm
410  wm transient .preferencesForm .
411 }
412 
413 .menubar.options add command -label "Background Colour" -command {
414  set bgc [tk_chooseColor -initialcolor $backgroundColour]
415  if {$bgc!=""} {
416  set backgroundColour $bgc
417  setBackgroundColour $backgroundColour
418  }
419 }
420 
421 menu .menubar.rungeKutta
422 .menubar.rungeKutta add command -label "Simulation" -command {
423  foreach {var text} $rkVars { set rkVarInput($var) [$var] }
424  set implicitSolver [implicit]
425  deiconifyRKDataForm
426  update idletasks
427  ::tk::TabToWindow $rkVarInput(initial_focus)
428  ensureWindowVisible .rkDataForm
429  grab set .rkDataForm
430  wm transient .rkDataForm .
431 } -underline 0
432 .menubar add cascade -label "Simulation" -menu .menubar.rungeKutta
433 
434 # special platform specific menus
435 menu .menubar.help
436 if {[tk windowingsystem] != "aqua"} {
437  .menubar.help add command -label "Minsky Documentation" -command {help Introduction}
438 }
439 .menubar add cascade -label Help -menu .menubar.help
440 
441 bind . <F1> topLevelHelp
442 bind .menubar.file <F1> {help File}
443 
444 
445 # placement of menu items in menubar
446 . configure -menu .menubar
447 
448 # controls toolbar
449 
450 labelframe .controls
451 label .controls.statusbar -text "t: 0 Δt: 0"
452 
453 # classic mode
454 set classicMode 0
455 
456 if {$classicMode} {
457  button .controls.run -text run -command runstop
458  button .controls.reset -text reset -command reset
459  button .controls.step -text step -command {step}
460 } else {
461  image create photo runButton -file "$minskyHome/icons/Play.gif"
462  image create photo stopButton -file "$minskyHome/icons/Pause.gif"
463  image create photo resetButton -file "$minskyHome/icons/Rewind.gif"
464  image create photo stepButton -file "$minskyHome/icons/Last.gif"
465  # iconic mode
466  button .controls.run -image runButton -height 25 -width 25 -command runstop
467  button .controls.reset -image resetButton -height 25 -width 25 -command reset
468  button .controls.step -image stepButton -height 25 -width 25 -command {step}
469 }
470 
471 image create photo recalculate -file "$minskyHome/icons/recalculate.gif"
472 button .controls.recalculate -image recalculate -height 25 -width 25 -command reset
473 image create photo rec -file "$minskyHome/icons/rec.gif"
474 image create photo runmode -file "$minskyHome/icons/runmode.gif"
475 image create photo recplay -file "$minskyHome/icons/recplay.gif"
476 
477 checkbutton .controls.rec -image rec -height 25 -width 25 -command toggleRecording -variable eventRecording -indicatoron 0
478 checkbutton .controls.runmode -image runmode -height 25 -width 25 -selectimage recplay -variable recordingReplay -command replay -indicatoron 0 -selectcolor $backgroundColour
479 checkbutton .controls.reverse -text "Rev" -command {
480  minsky.reverse $reverse} -variable reverse
481 
482 tooltip .controls.recalculate "Recalculate"
483 tooltip .controls.rec "Record"
484 tooltip .controls.runmode "Simulate/Recording Replay"
485 tooltip .controls.reverse "Reverse simulation"
486 tooltip .controls.run "Run/Stop"
487 tooltip .controls.reset "Reset simulation"
488 tooltip .controls.step "Step simulation"
489 
490 # enable auto-repeat on step button
491 bind .controls.step <ButtonPress-1> {set buttonPressed 1; autoRepeatButton .controls.step}
492 bind . <ButtonRelease-1> {set buttonPressed 0}
493 
494 proc generateBookmarkMenu {} {
495  .menubar.bookmarks delete 0 end
496  .menubar.bookmarks add command -label "Bookmark this position" -command addBookMark
497  .menubar.bookmarks.deleteMenu delete 0 end
498  .menubar.bookmarks add cascade -label "Delete ..." -menu .menubar.bookmarks.deleteMenu
499  .menubar.bookmarks add separator
500  set p 0
501  foreach i [minsky.canvas.model.bookmarkList] {
502  .menubar.bookmarks add command -label $i -command "canvas.model.gotoBookmark $p; canvas.requestRedraw"
503  .menubar.bookmarks.deleteMenu add command -label $i -command "canvas.model.deleteBookmark $p"
504  incr p
505  }
506 }
507 
508 proc addBookMark {} {
509  toplevel .bookMarkDialog
510  frame .bookMarkDialog.name
511  label .bookMarkDialog.name.text -text "Bookmark name"
512  entry .bookMarkDialog.name.val
513  pack .bookMarkDialog.name.text .bookMarkDialog.name.val -side left
514  pack .bookMarkDialog.name
515  buttonBar .bookMarkDialog {
516  minsky.canvas.model.addBookmark [.bookMarkDialog.name.val get]
517  }
518  ensureWindowVisible .bookMarkDialog
519  wm transient .bookMarkDialog
520  focus .bookMarkDialog.name.val
521  grab set .bookMarkDialog
522 }
523 
524 # self submitting script that continues while a button is pressed
525 proc invokeButton button {
526  global buttonPressed
527  if {$buttonPressed} {
528  $button invoke
529  update
530  autoRepeatButton $button
531  }
532 }
533 
534 proc autoRepeatButton button {
535  after 500 invokeButton $button
536 }
537 
538 proc setSimulationDelay {delay} {
539  # on loading a model, slider is adjusted, which causes
540  # simulationDelay to be set unnecessarily, marking the model
541  # dirty
542  if {$delay != [simulationDelay]} {
543  pushFlags
544  simulationDelay $delay
545  popFlags
546  }
547 }
548 
549 label .controls.slowSpeed -text "slow"
550 label .controls.fastSpeed -text "fast"
551 scale .controls.simSpeed -variable delay -command setSimulationDelay -to 0 -from 12 -length 150 -label "Simulation Speed" -orient horizontal -showvalue 0
552 
553 
554 
555 pack .controls.recalculate .controls.rec .controls.runmode .controls.reverse .controls.run .controls.reset .controls.step .controls.slowSpeed .controls.simSpeed .controls.fastSpeed -side left
556 pack .controls.statusbar -side right -fill x
557 
558 grid .controls -row 0 -column 0 -columnspan 1000 -sticky ew
559 
560 menu .menubar.file.recent
561 
562 menu .exportPlots
563 .exportPlots add command -label "as SVG" -command {minsky.renderAllPlotsAsSVG [file rootname $fname]}
564 .exportPlots add command -label "as CSV" -command {minsky.exportAllPlotsAsCSV [file rootname $fname]}
565 
566 # File menu
567 .menubar.file add command -label "About Minsky" -command aboutMinsky
568 .menubar.file add command -label "Upgrade" -command {openURL http://www.patreon.com/hpcoder}
569 .menubar.file add command -label "New System" -command newSystem -underline 0 -accelerator $meta_menu-N
570 .menubar.file add command -label "Open" -command openFile -underline 0 -accelerator $meta_menu-O
571 .menubar.file add cascade -label "Recent Files" -menu .menubar.file.recent
572 .menubar.file add command -label "Library" -command "openURL https://github.com/highperformancecoder/minsky-models"
573 
574 .menubar.file add command -label "Save" -command save -underline 0 -accelerator $meta_menu-S
575 .menubar.file add command -label "SaveAs" -command saveAs -underline 4 -accelerator $meta_menu-A
576 .menubar.file add command -label "Insert File as Group" -command insertFile
577 .menubar.file add command -label "Import Vensim MDL file" -command importMDL
578 
579 .menubar.file add command -label "Dimensional Analysis" -command {
580  dimensionalAnalysis
581  tk_messageBox -type ok -icon info -message "Dimension Analysis passed"
582 }
583 
584 .menubar.file add command -label "Export resolution factor: [minsky.canvas.resolutionScaleFactor]" -command {setExportResolutionFactor minsky.canvas}
585 .menubar.file add command -label "Export Canvas" -command exportCanvas
586 .menubar.file add cascade -label "Export Plots" -menu .exportPlots
587 .menubar.file add checkbutton -label "Log simulation" -variable simLogging \
588  -command getLogVars
589 .menubar.file add checkbutton -label "Recording" -command toggleRecording -variable eventRecording
590 .menubar.file add checkbutton -label "Replay recording" -command replay -variable recordingReplay
591 
592 .menubar.file add command -label "Quit" -command exit -underline 0 -accelerator $meta_menu-Q
593 .menubar.file add separator
594 .menubar.file add command -foreground #5f5f5f -label "Debugging Use"
595 .menubar.file add command -label "Redraw" -command canvas.requestRedraw
596 .menubar.file add command -label "Object Browser" -command obj_browser
597 .menubar.file add command -label "Select items" -command selectItems
598 .menubar.file add command -label "Command" -command cli
599 
600 .menubar.file configure -postcommand {
601  .menubar.file entryconfigure "Export resolution factor:*" -label "Export resolution factor: [minsky.canvas.resolutionScaleFactor]"
602 }
603 
604 proc imageFileTypes {} {
605  global tcl_platform
606  set fileTypes {{"SVG" .svg TEXT} {"PDF" .pdf TEXT} {"Postscript" .eps TEXT} {"Portable Network Graphics" .png TEXT}}
607  if {$tcl_platform(platform)=="windows"} {lappend fileTypes {"EMF" .emf TEXT}}
608  return $fileTypes
609 }
610 
611 proc setExportResolutionFactor {setter} {
612  toplevel .resolutionFactor
613  wm title .resolutionFactor "Export resolution scale factor"
614  ttk::spinbox .resolutionFactor.value -from 1 -to 1000 -increment 1
615  pack .resolutionFactor.value
616  .resolutionFactor.value set [$setter.resolutionScaleFactor]
617  buttonBar .resolutionFactor "$setter.resolutionScaleFactor \[.resolutionFactor.value get\]"
618 }
619 
620 
621 proc renderImage {filename type surf} {
622  global tcl_platform
623  if [string match -nocase *.svg "$filename"] {
624  $surf.renderToSVG "$filename"
625  } elseif [string match -nocase *.pdf "$filename"] {
626  $surf.renderToPDF "$filename"
627  } elseif {[string match -nocase *.ps "$filename"] || [string match -nocase *.eps "$filename"]} {
628  $surf.renderToPS "$filename"
629  } elseif {[string match -nocase *.png "$filename"]} {
630  $surf.renderToPNG "$filename"
631  } elseif {$tcl_platform(platform)=="windows" && [string match -nocase *.emf "$filename"]} {
632  $surf.renderToEMF "$filename"
633  } else {
634  switch $type {
635  "SVG" {$surf.renderToSVG "$filename.svg"}
636  "PDF" {$surf.renderToPDF "$filename.pdf"}
637  "PNG" {$surf.renderToPNG "$filename.png"}
638  "EMF" {$surf.renderToEMF "$filename.emf"}
639  "Postscript" {$surf.renderToPS "$filename.eps"}
640  default {return false}
641  }
642  }
643  return true
644 }
645 
646 proc exportCanvas {} {
647  global workDir type fname preferences tabSurface
648 
649  set fileTypes [imageFileTypes]
650  lappend fileTypes {"LaTeX" .tex TEXT} {"Matlab" .m TEXT}
651  set f [tk_getSaveFile -filetypes $fileTypes \
652  -initialdir $workDir -typevariable type -initialfile [file rootname [file tail $fname]]]
653  if {$f==""} return
654  set workDir [file dirname $f]
655  # extract the surface name from the current tab, for #912
656  set surf [lindex [.tabs tabs] [.tabs index current]].canvas
657  if [renderImage $f $type $tabSurface([.tabs tab current -text])] return
658  if {[string match -nocase *.tex "$f"]} {
659  latex "$f" $preferences(wrapLaTeXLines)
660  } elseif {[string match -nocase *.m "$f"]} {
661  matlab "$f"
662  } else {
663  switch $type {
664  "LaTeX" {latex "$f.tex" $preferences(wrapLaTeXLines)}
665  "Matlab" {matlab "$f.m"}
666  }
667  }
668 }
669 
670 
671 proc getLogVars {} {
672  global varNames allLogVars varIds
673  set varNames {}
674  set varIds {}
675  set allLogVars 0
676  toplevel .logVars
677  frame .logVars.buttons
678  button .logVars.buttons.ok -text OK -command logVarsOK
679  checkbutton .logVars.buttons.all -text All -variable allLogVars -command {
680  .logVars.selection.selection selection [expr $allLogVars?"set":"clear"] 0 end
681  }
682  pack .logVars.buttons.ok .logVars.buttons.all -side left
683 
684  foreach v [variableValues.#keys] {
685  if {![regexp "^constant:" $v]} {
686  getValue $v
687  lappend varNames [minsky.value.name]
688  lappend varIds $v
689  }
690  }
691  frame .logVars.selection
692  listbox .logVars.selection.selection -listvariable varNames -selectmode extended -height 30 -yscrollcommand ".logVars.selection.vscroll set" -selectforeground blue
693  scrollbar .logVars.selection.vscroll -orient vertical -command ".logVars.selection.selection yview"
694  pack .logVars.selection.selection -fill both -side left -expand y
695  pack .logVars.selection.vscroll -fill y -side left -expand y
696  pack .logVars.buttons .logVars.selection
697 
698  ensureWindowVisible .logVars
699  grab set .logVars
700  wm transient .logVars
701 }
702 
703 proc logVarsOK {} {
704  global workDir varIds
705  set indices [.logVars.selection.selection curselection]
706 
707  foreach i $indices {lappend vars [lindex $varIds $i]}
708  logVarList $vars
709  destroy .logVars
710  openLogFile [tk_getSaveFile -defaultextension .dat -initialdir $workDir]
711 }
712 
713 
714 
715 
716 .menubar.edit add command -label "Undo" -command "undo 1" -accelerator $meta_menu-Z
717 .menubar.edit add command -label "Redo" -command "undo -1" -accelerator $meta_menu-Y
718 .menubar.edit add command -label "Cut" -command cut -accelerator $meta_menu-X
719 .menubar.edit add command -label "Copy" -command "minsky.copy" -accelerator $meta_menu-C
720 .menubar.edit add command -label "Paste" -command "minsky.paste" -accelerator $meta_menu-V
721 .menubar.edit add command -label "Group selection" -command "minsky.createGroup" -accelerator $meta_menu-G
722 .menubar.edit add command -label "Dimensions" -command dimensionsDialog
723 .menubar.edit add command -label "Remove units" -command minsky.deleteAllUnits
724 .menubar.edit add command -label "Randomize layout" -command minsky.randomLayout
725 .menubar.edit add command -label "Auto layout" -command minsky.autoLayout
726 
727 proc getClipboard {} {
728  set contents ""
729  catch {clipboard get -type UTF8_STRING} contents
730  return contents
731 }
732 
733 proc getClipboard {} {
734  set contents ""
735  catch {clipboard get -type UTF8_STRING} contents
736  return contents
737 }
738 
739 proc togglePaste {} {
740  if {[getClipboard]==""} {
741  .menubar.edit entryconfigure "Paste" -state disabled
742  } else {
743  .menubar.edit entryconfigure "Paste" -state normal
744  }
745 }
746 
747 proc undo {delta} {
748  # do not record changes to state from the undo command
749  doPushHistory 0
750  minsky.undo $delta
751  minsky.canvas.requestRedraw
753  doPushHistory 1
754 }
755 
756 proc cut {} {
757  minsky.cut
758 }
759 
760 proc dimensionsDialog {} {
761  populateMissingDimensions
762  toplevel .dimensions
763  grid [button .dimensions.cancel -text Cancel -command "destroy .dimensions"] \
764  [button .dimensions.ok -text OK -command {
765  set colRows [grid size .dimensions]
766  for {set i 2} {$i<[lindex $colRows 1]} {incr i} {
767  set dim [.dimensions.g${i}_dim get]
768  if {$dim!=""} {
769  set d [dimensions.@elem $dim]
770  $d.type [.dimensions.g${i}_type get]
771  if [info exists timeFormatStrings([.dimensions.g${i}_units get])] {
772  $d.units $timeFormatStrings([.dimensions.g${i}_units get])
773  } else {
774  $d.units [.dimensions.g${i}_units get]
775  }
776  }
777  }
778  imposeDimensions
779  destroy .dimensions
780  reset
781  }]
782  grid [label .dimensions.g1_dim -text Dimension] \
783  [label .dimensions.g1_type -text Type]\
784  [label .dimensions.g1_units -text "Units/Format"]
785  tooltip .dimensions.g1_units "Value type: enter a unit string, eg m/s; time type: enter a strftime format string, eg %Y-%m-%d %H:%M:%S, or %Y-Q%Q"
786 
787  set colRows [grid size .dimensions]
788  for {set i [lindex $colRows 1]} {$i<[dimensions.size]+3} {incr i} {
789  grid [entry .dimensions.g${i}_dim] \
790  [ttk::combobox .dimensions.g${i}_type -state readonly \
791  -values {string value time}] \
792  [ttk::combobox .dimensions.g${i}_units \
793  -postcommand "dimFormatPopdown .dimensions.g${i}_units \[.dimensions.g${i}_type get\] {}"
794  ]
795  }
796  set i 2
797  foreach dim [dimensions.#keys] {
798  set d [dimensions.@elem $dim]
799  .dimensions.g${i}_dim delete 0 end
800  .dimensions.g${i}_dim insert 0 $dim
801  .dimensions.g${i}_type set [$d.type]
802  .dimensions.g${i}_units delete 0 end
803  .dimensions.g${i}_units insert 0 [$d.units]
804  dimFormatPopdown .dimensions.g${i}_units [$d.type] {}
805  incr i
806  }
807 }
808 
809 
810 
811 array set timeFormatStrings {
812  "1999-Q4" "%Y-Q%Q"
813  "1999" "%Y"
814  "12/31/99" "%m/%d/%y"
815  "12/31/1999" "%m/%d/%Y"
816  "31/12/99" "%d/%m/%y"
817  "31/12/1999" "%d/%m/%Y"
818  "1999-12-31T13:37:46" "%Y-%m-%dT%H:%M:%S"
819  "12/31/1999 01:37 PM" "%m/%d/%Y %I:%M %p"
820  "12/31/99 01:37 PM" "%m/%d/%y %I:%M %p"
821  "12/31/1999 13:37 PM" "%m/%d/%Y %H:%M %p"
822  "12/31/99 13:37 PM" "%m/%d/%y %H:%M %p"
823  "Friday, December 31, 1999" "%A, %B %d, %Y"
824  "Dec 31, 99" "%b %d, %y"
825  "Dec 31, 1999" "%b %d, %Y"
826  "31. Dec. 1999" "%d. %b. %Y"
827  "December 31, 1999" "%B %d, %Y"
828  "31. December 1999" "%d. %B %Y"
829  "Fri, Dec 31, 99" "%a, %b %d, %y"
830  "Fri 31/Dec 99" "%a %d/%b %y"
831  "Fri, Dec 31, 1999" "%a, %b %d, %Y"
832  "Friday, December 31, 1999" "%A, %B %d, %Y"
833  "12-31" "%m-%d"
834  "99-12-31" "%y-%m-%d"
835  "1999-12-31" "%Y-%m-%d"
836  "12/99" "%m/%y"
837  "Dec 31" "%b %d"
838  "December" "%B"
839  "4th quarter 99" "%Qth quarter %y"
840 }
841 
842 proc rewriteTimeComboBox {comboBox} {
843  global timeFormatStrings
844  if [info exists timeFormatStrings([$comboBox get])] {
845  $comboBox set $timeFormatStrings([$comboBox get])
846  }
847 }
848 
849 # If comboBox is a format combo box for a field of \a type, then set up rewrite strings, then execute \a onSelect
850 proc dimFormatPopdown {comboBox type onSelect} {
851  global timeFormatStrings
852  switch $type {
853  string {
854  $comboBox configure -values {}
855  $comboBox set {}
856  bind $comboBox <<ComboboxSelected>> $onSelect
857  }
858  value {
859  $comboBox configure -values {}
860  bind $comboBox <<ComboboxSelected>> $onSelect
861  }
862  time {
863  $comboBox configure -values [lsort [array names timeFormatStrings]]
864  bind $comboBox <<ComboboxSelected>> "rewriteTimeComboBox $comboBox; $onSelect"
865  }
866  }
867 }
868 
869 proc pasteAt {} {
870  minsky.paste
871  canvas.mouseMove [get_pointer_x .wiring.canvas] [get_pointer_y .wiring.canvas]
872 }
873 
874 wm protocol . WM_DELETE_WINDOW exit
875 # keyboard accelerators
876 bind . <$meta-s> save
877 bind . <$meta-S> save
878 bind . <$meta-a> saveAs
879 bind . <$meta-A> saveAs
880 bind . <$meta-o> openFile
881 bind . <$meta-O> openFile
882 bind . <$meta-n> newSystem
883 bind . <$meta-N> newSystem
884 bind . <$meta-q> exit
885 bind . <$meta-Q> exit
886 bind . <$meta-y> "undo -1"
887 bind . <$meta-Y> "undo -1"
888 bind . <$meta-z> "undo 1"
889 bind . <$meta-Z> "undo 1"
890 bind . <$meta-x> {minsky.cut}
891 bind . <$meta-X> {minsky.cut}
892 bind . <$meta-c> {minsky.copy}
893 bind . <$meta-C> {minsky.copy}
894 bind . <$meta-v> {pasteAt}
895 bind . <$meta-V> {pasteAt}
896 bind . <$meta-g> {minsky.createGroup}
897 bind . <$meta-G> {minsky.createGroup}
898 
899 # tabbed manager
900 ttk::notebook .tabs -padding 0
901 ttk::notebook::enableTraversal .tabs
902 # disable arrow bindings for switching between tabs, as we want to use these on the canvas
903 bind .tabs <Left> {}
904 bind .tabs <Right> {}
905 grid .tabs -column 0 -row 10 -sticky news
906 grid columnconfigure . 0 -weight 1
907 grid rowconfigure . 10 -weight 1
908 
909 # utility for creating OK/Cancel button bar
910 proc buttonBar {window okProc} {
911  frame $window.buttonBar
912  button $window.buttonBar.ok -text "OK" -command "okAction \{$okProc\} $window"
913  button $window.buttonBar.cancel -text "Cancel" -command "cancelWin $window"
914  pack $window.buttonBar.cancel $window.buttonBar.ok -side left
915  pack $window.buttonBar -side top
916  bind $window <Key-Return> "$window.buttonBar.ok invoke"
917  bind $window <Key-Escape> "$window.buttonBar.cancel invoke"
918 }
919 
920 proc okAction {okProc window} {
921  if [catch $okProc msg] {
922  tk_messageBox -icon error -parent $window -message $msg
923  raise $window
924  return
925  }
926  cancelWin $window
927 }
928 
929 proc cancelWin window {
930  grab release $window
931  destroy $window
932 }
933 
934 proc ensureWindowVisible window {
935  if {![winfo ismapped $window]} {
936  tkwait visibility $window
937  }
938 }
939 
940 # pop up a text entry widget to capture some user input
941 # @param win is top level window name
942 # @param init initialises the entry widget
943 # @param okproc gets executed when OK pressed. Use [$win.entry get] to return user value
944 proc textEntryPopup {win init okproc} {
945  if {![winfo exists $win]} {
946  toplevel $win
947  entry $win.entry
948  pack $win.entry -side top -ipadx 50
949  buttonBar $win $okproc
950  } else {
951  wm deiconify $win
952  }
953  $win.entry delete 0 end
954  $win.entry insert 0 $init
955  wm transient $win
956  focus $win.entry
958  grab set $win
959 
960 }
961 
962 bind .tabs <<contextMenu>> {
963  set windows [.tabs tabs]
964  set idx [.tabs identify tab %x %y]
965  if {$idx<[llength $windows]} {
966  .wiring.context delete 0 end
967  .wiring.context add command -label Help -command "
968  help $helpTopics([lindex $windows $idx])"
969  tk_popup .wiring.context %X %Y
970  }
971 }
972 
973 proc addTab {window label surface} {
974  image create cairoSurface rendered$window -surface $surface
975  ttk::frame .$window
976  global canvasHeight canvasWidth tabSurface helpTopics
977  label .$window.canvas -image rendered$window -height $canvasHeight -width $canvasWidth
978  .tabs add .$window -text $label -padding 0
979  set tabSurface($label) $surface
980  set helpTopics(.$window) tabs:$label
981 }
982 
983 # add the tabbed windows
984 addTab wiring "Wiring" minsky.canvas
985 addTab equations "Equations" minsky.equationDisplay
986 pack .equations.canvas -fill both -expand 1
987 
988 .tabs select 0
989 
990 proc hoverMouseTab {tabId} {
991  set tab [lindex [.tabs tabs] [.tabs index current]]
992  $tabId.displayDelayedTooltip [get_pointer_x $tab.canvas] [get_pointer_y $tab.canvas]
993 }
994 
995 # reset hoverMouse timer
996 proc wrapHoverMouseTab {tabId op x y} {
997  after cancel hoverMouseTab $tabId
998  # ignore any exceptions
999  catch {$tabId.$op $x $y}
1000  after 3000 hoverMouseTab $tabId
1001 }
1002 
1003 proc tabContext {x y X Y} {
1004  switch [lindex [.tabs tabs] [.tabs index current]] {
1005  .variables {
1006  .variables.context delete 0 end
1007  set r [variableTab.rowY $y]
1008  switch [variableTab.clickType $x $y] {
1009  background {}
1010  internal {
1011  set varName [variableTab.getVarName $r]
1012  .variables.context add command -label "Remove $varName from tab" -command "variableTab.toggleVarDisplay $r; variableTab.requestRedraw"
1013  }
1014  }
1015  tk_popup .variables.context $X $Y
1016  }
1017  .plts {
1018  .plts.context delete 0 end
1019  if [getPlotTabItemAt $x $y] {
1020  .plts.context add command -label "Remove plot from tab" -command "plotTab.togglePlotDisplay; plotTab.requestRedraw"
1021  }
1022  tk_popup .plts.context $X $Y
1023  }
1024  }
1025 }
1026 
1027 source $minskyHome/godley.tcl
1028 source $minskyHome/plots.tcl
1029 source $minskyHome/group.tcl
1030 source $minskyHome/wiring.tcl
1031 source $minskyHome/csvImport.tcl
1032 source $minskyHome/ravel.tcl
1033 source $minskyHome/variablePane.tcl
1034 
1035 pack .wiring.canvas -fill both -expand 1
1036 
1037 proc setScrollBars {} {
1038  switch [lindex [.tabs tabs] [.tabs index current]] {
1039  .wiring {
1040  set x0 [expr (10000-[minsky.canvas.model.x])/20000.0]
1041  set y0 [expr (10000-[minsky.canvas.model.y])/20000.0]
1042  .hscroll set $x0 [expr $x0+[winfo width .wiring.canvas]/20000.0]
1043  .vscroll set $y0 [expr $y0+[winfo height .wiring.canvas]/20000.0]
1044  }
1045  .equations {
1046  if {[equationDisplay.width]>0} {
1047  set x0 [expr [equationDisplay.offsx]/[equationDisplay.width]]
1048  .hscroll set $x0 [expr $x0+[winfo width .wiring.canvas]/[equationDisplay.width]]
1049  } else {.hscroll set 0 1}
1050  if {[equationDisplay.height]>0} {
1051  set y0 [expr [equationDisplay.offsx]/[equationDisplay.height]]
1052  .vscroll set $y0 [expr $y0+[winfo height .wiring.canvas]/[equationDisplay.height]]
1053  } else {.vscroll set 0 1}
1054  }
1055  .parameters {
1056  set x0 [expr (10000-[parameterTab.offsx])/20000.0]
1057  set y0 [expr (10000-[parameterTab.offsy])/20000.0]
1058  .hscroll set $x0 [expr $x0+[winfo width .parameters.canvas]/20000.0]
1059  .vscroll set $y0 [expr $y0+[winfo height .parameters.canvas]/20000.0]
1060  }
1061  .variables {
1062  set x0 [expr (10000-[variableTab.offsx])/20000.0]
1063  set y0 [expr (10000-[variableTab.offsy])/20000.0]
1064  .hscroll set $x0 [expr $x0+[winfo width .variables.canvas]/20000.0]
1065  .vscroll set $y0 [expr $y0+[winfo height .variables.canvas]/20000.0]
1066  }
1067  .plts {
1068  set x0 [expr (10000-[plotTab.offsx])/20000.0]
1069  set y0 [expr (10000-[plotTab.offsy])/20000.0]
1070  .hscroll set $x0 [expr $x0+[winfo width .plts.canvas]/20000.0]
1071  .vscroll set $y0 [expr $y0+[winfo height .plts.canvas]/20000.0]
1072  }
1073  .gdlys {
1074  set x0 [expr (10000-[godleyTab.offsx])/20000.0]
1075  set y0 [expr (10000-[godleyTab.offsy])/20000.0]
1076  .hscroll set $x0 [expr $x0+[winfo width .gdlys.canvas]/20000.0]
1077  .vscroll set $y0 [expr $y0+[winfo height .gdlys.canvas]/20000.0]
1078  }
1079  }
1080 }
1081 
1082 bind .tabs <<NotebookTabChanged>> {setScrollBars}
1083 
1084 proc panCanvas {offsx offsy} {
1085  global preferences
1086  switch [lindex [.tabs tabs] [.tabs index current]] {
1087  .wiring {
1088  minsky.canvas.model.moveTo $offsx $offsy
1089  canvas.requestRedraw
1090  }
1091  .equations {
1092  equationDisplay.offsx $offsx
1093  equationDisplay.offsy $offsy
1094  equationDisplay.requestRedraw
1095  }
1096  .parameters {
1097  parameterTab.offsx $offsx
1098  parameterTab.offsy $offsy
1099  parameterTab.requestRedraw
1100  }
1101  .variables {
1102  variableTab.offsx $offsx
1103  variableTab.offsy $offsy
1104  variableTab.requestRedraw
1105  }
1106  .plts {
1107  plotTab.offsx $offsx
1108  plotTab.offsy $offsy
1109  plotTab.requestRedraw
1110  }
1111  .gdlys {
1112  godleyTab.offsx $offsx
1113  godleyTab.offsy $offsy
1114  godleyTab.requestRedraw
1115  }
1116  }
1118 }
1119 
1120 
1121 ttk::sizegrip .sizegrip
1122 proc scrollCanvases {xyview args} {
1123  set win [lindex [.tabs tabs] [.tabs index current]]
1124  set ww [winfo width $win]
1125  set wh [winfo height $win]
1126  switch $win {
1127  .wiring {
1128  set x [minsky.canvas.model.x]
1129  set y [minsky.canvas.model.y]
1130  set w [expr 10*$ww]
1131  set h [expr 10*$wh]
1132  set x1 [expr 0.5*$w]
1133  set y1 [expr 0.5*$h]
1134  }
1135  .equations {
1136  set x [equationDisplay.offsx]
1137  set y [equationDisplay.offsy]
1138  set x1 0
1139  set y1 0
1140  set w [equationDisplay.width]
1141  set h [equationDisplay.height]
1142  }
1143  .parameters {
1144  set x [parameterTab.offsx]
1145  set y [parameterTab.offsy]
1146  set w [expr 10*$ww]
1147  set h [expr 10*$wh]
1148  set x1 [expr 0.5*$w]
1149  set y1 [expr 0.5*$h]
1150  }
1151  .variables {
1152  set x [variableTab.offsx]
1153  set y [variableTab.offsy]
1154  set w [expr 10*$ww]
1155  set h [expr 10*$wh]
1156  set x1 [expr 0.5*$w]
1157  set y1 [expr 0.5*$h]
1158  }
1159  .plts {
1160  set x [plotTab.offsx]
1161  set y [plotTab.offsy]
1162  set w [expr 10*$ww]
1163  set h [expr 10*$wh]
1164  set x1 [expr 0.5*$w]
1165  set y1 [expr 0.5*$h]
1166  }
1167  .gdlys {
1168  set x [godleyTab.offsx]
1169  set y [godleyTab.offsy]
1170  set w [expr 10*$ww]
1171  set h [expr 10*$wh]
1172  set x1 [expr 0.5*$w]
1173  set y1 [expr 0.5*$h]
1174  }
1175  }
1176  switch [lindex $args 0] {
1177  moveto {
1178  switch $xyview {
1179  xview {panCanvas [expr $x1-$w*[lindex $args 1]] $y}
1180  yview {panCanvas $x [expr $y1-$h*[lindex $args 1]]}
1181  }
1182  }
1183  scroll {
1184  switch [lindex $args 2] {
1185  units {set incr [expr [lindex $args 1]*0.01]}
1186  # page corresponds to one full screens worth
1187  pages {set incr [expr [lindex $args 1]*0.1]}
1188  }
1189  switch $xyview {
1190  xview {panCanvas [expr $x-$incr*$w] $y}
1191  yview {panCanvas $x [expr $y-$incr*$h]}
1192  }
1193  }
1194  }
1195 }
1196 scrollbar .vscroll -orient vertical -command "scrollCanvases yview"
1197 scrollbar .hscroll -orient horiz -command "scrollCanvases xview"
1198 update
1200 
1201 bind . <Key-Prior> {scrollCanvases yview scroll -1 pages}
1202 bind . <Key-Next> {scrollCanvases yview scroll 1 pages}
1203 bind . <Key-Home> {scrollCanvases xview scroll -1 pages}
1204 bind . <Key-End> {scrollCanvases xview scroll 1 pages}
1205 
1206 # adjust cursor for pan mode
1207 if {[tk windowingsystem] == "aqua"} {
1208  set panIcon closedhand
1209 } else {
1210  set panIcon fleur
1211 }
1212 
1213 
1214 
1215 # equations pan mode
1216 .equations.canvas configure -cursor $panIcon
1217 bind .equations.canvas <Button-1> {
1218  set panOffsX [expr %x-[equationDisplay.offsx]]
1219  set panOffsY [expr %y-[equationDisplay.offsy]]
1220 }
1221 bind .equations.canvas <B1-Motion> {panCanvas [expr %x-$panOffsX] [expr %y-$panOffsY]}
1222 
1223 grid .sizegrip -row 999 -column 999
1224 grid .vscroll -column 999 -row 10 -rowspan 989 -sticky ns
1225 grid .hscroll -row 999 -column 0 -columnspan 999 -sticky ew
1226 
1227 image create photo zoomOutImg -file $minskyHome/icons/zoomOut.gif
1228 button .controls.zoomOut -image zoomOutImg -height 24 -width 37 \
1229  -command {zoom 0.91}
1230 tooltip .controls.zoomOut "Zoom Out"
1231 
1232 image create photo zoomInImg -file $minskyHome/icons/zoomIn.gif
1233 button .controls.zoomIn -image zoomInImg -height 24 -width 37 \
1234  -command {zoom 1.1}
1235 tooltip .controls.zoomIn "Zoom In"
1236 
1237 image create photo zoomOrigImg -file $minskyHome/icons/zoomOrig.gif
1238 button .controls.zoomOrig -image zoomOrigImg -height 24 -width 37 \
1239  -command {
1240  if {[minsky.model.zoomFactor]>0} {
1241  zoom [expr 1/[minsky.model.relZoom]]
1242  } else {
1243  minsky.model.setZoom 1
1244  }
1245  recentreCanvas
1246  }
1247 tooltip .controls.zoomOrig "Reset Zoom/Origin"
1248 
1249 image create photo zoomFitImg -file $minskyHome/icons/zoomFit.gif
1250 button .controls.zoomFit -image zoomFitImg -height 24 -width 37 \
1251  -command {
1252  set cb [minsky.canvas.model.cBounds]
1253  set z1 [expr double([winfo width .wiring.canvas])/([lindex $cb 2]-[lindex $cb 0])]
1254  set z2 [expr double([winfo height .wiring.canvas])/([lindex $cb 3]-[lindex $cb 1])]
1255  if {$z2<$z1} {set z1 $z2}
1256  set x [expr -0.5*([lindex $cb 2]+[lindex $cb 0])]
1257  set y [expr -0.5*([lindex $cb 3]+[lindex $cb 1])]
1258  zoomAt $x $y $z1
1259  recentreCanvas
1260  }
1261 tooltip .controls.zoomFit "Zoom to fit"
1262 pack .controls.zoomOut .controls.zoomIn .controls.zoomOrig .controls.zoomFit -side left
1263 
1264 set delay [simulationDelay]
1265 
1266 proc runstop {} {
1267  global classicMode
1268  if [running] {
1269  running 0
1270  doPushHistory 1
1271  if {$classicMode} {
1272  .controls.run configure -text run
1273  } else {
1274  .controls.run configure -image runButton
1275  }
1276  } else {
1277  running 1
1278  doPushHistory 0
1279  if {$classicMode} {
1280  .controls.run configure -text stop
1281  } else {
1282  .controls.run configure -image stopButton
1283  }
1284  step
1285  simulate
1286  }
1287 }
1288 
1289 proc step {} {
1290  global recordingReplay eventRecordR simTMax simTStart
1291  if {$recordingReplay} {
1292  if {[gets $eventRecordR cmd]>=0} {
1293  eval $cmd
1294  update
1295  } else {
1296  runstop
1297  }
1298  } else {
1299  # run simulation
1300  global preferences
1301  if {[catch minsky.step errMsg options] && [running]} {runstop}
1302  if {[minsky.t0]>[t] || [minsky.tmax]<[t]} {runstop}
1303  .controls.statusbar configure -text "t: [t] Δt: [format %g [deltaT]]"
1304  if $preferences(godleyDisplay) redrawAllGodleyTables
1305  update
1306  return -options $options $errMsg
1307  }
1308 }
1309 
1310 
1311 proc simulate {} {
1312  uplevel #0 {
1313  if [running] {
1314  set d [expr int(pow(10,$delay/4.0))]
1315  after $d {
1316  if [running] {
1317  step
1318  simulate
1319  }
1320  }
1321  }
1322  }
1323 }
1324 
1325 proc reset {} {
1326  global recordingReplay eventRecordR simLogging eventRecording
1327  if {$eventRecording} {
1328  set eventRecording 0
1330  return
1331  }
1332  running 0
1333  if {$recordingReplay} {
1334  seek $eventRecordR 0 start
1335  model.clear
1336  canvas.requestRedraw
1337  } else {
1338  set tstep 0
1339  set simLogging 0
1340  closeLogFile
1341  # delay throwing exception to allow display to be updated
1342  set err [catch minsky.reset result]
1343  .controls.statusbar configure -text "t: 0 Δt: 0"
1344  .controls.run configure -image runButton
1345 
1346  redrawAllGodleyTables
1347  return -code $err $result
1348  }
1349 }
1350 
1351 
1352 
1353 proc populateRecentFiles {} {
1354  global recentFiles preferences
1355  .menubar.file.recent delete 0 end
1356  if {[llength $recentFiles]>$preferences(nRecentFiles)} {
1357  set recentFiles [lreplace $recentFiles $preferences(nRecentFiles) end]
1358  }
1359  foreach f $recentFiles {
1360  .menubar.file.recent insert 0 command -label "[file tail $f]" \
1361  -command "openNamedFile \"[regsub -all {\\} $f /]\""
1362  }
1363 }
1365 
1366 # load/save
1367 
1368 proc openFile {} {
1369  global fname workDir preferences
1370  set ofname [tk_getOpenFile -multiple 1 -filetypes {
1371  {Minsky {.mky}} {Ravel {.rvl}} {XML {.xml}} {All {.*}}} -initialdir $workDir]
1372  if [string length $ofname] {eval openNamedFile $ofname}
1373 }
1374 
1375 proc autoBackupName {} {
1376  global fname
1377  return "$fname#"
1378 }
1379 proc openNamedFile {ofname} {
1380  global fname workDir preferences
1381  newSystem
1382  setFname $ofname
1383 
1384  if {[file exists [autoBackupName]] && [tk_messageBox -message "Auto save file exists, do you wish to load it" -type yesno]=="yes"} {
1385  eval minsky.load {[autoBackupName]}
1386  } else {
1387  eval minsky.load {$ofname}
1388  file delete -- [autoBackupName]
1389  }
1390  # setting simulationDelay causes the edited (dirty) flag to be set, amongst other things
1391  pushFlags
1392  doPushHistory 0
1393  setAutoSaveFile [autoBackupName]
1394 
1395  # minsky.load resets minsky.multipleEquities and other preference, so restore preferences
1396  minsky.multipleEquities $preferences(multipleEquities)
1397  setGodleyDisplayValue $preferences(godleyDisplay) $preferences(godleyDisplayStyle)
1398 
1400 
1401  .controls.simSpeed set [simulationDelay]
1402  # force update canvas size to ensure model is displayed correctly
1403  update
1404  canvas.requestRedraw
1405  # not sure why this is needed, but initial draw doesn't happen without it
1406  event generate .wiring.canvas <Expose>
1407  update
1408  doPushHistory 1
1409  pushHistory
1410  popFlags
1411 }
1412 
1413 proc insertFile {} {
1414  global workDir
1415  set fname [tk_getOpenFile -multiple 1 -filetypes {
1416  {Minsky {.mky}} {Ravel {.rvl}} {XML {.xml}} {All {.*}}} -initialdir $workDir]
1417  eval insertGroupFromFile $fname
1418 }
1419 
1420 proc importMDL {} {
1421  global workDir
1422  newSystem
1423  set fname [tk_getOpenFile -multiple 1 -filetypes {
1424  {Vensim {.mdl}} {All {.*}}} -initialdir $workDir]
1425  eval importVensim $fname
1426  minsky.model.autoLayout
1427 }
1428 
1429 # adjust canvas so that -ve coordinates appear on canvas
1430 proc recentreCanvas {} {
1431  switch [lindex [.tabs tabs] [.tabs index current]] {
1432  .wiring {canvas.recentre}
1433  .equations {
1434  equationDisplay.offsx 0
1435  equationDisplay.offsy 0
1436  equationDisplay.requestRedraw
1437  }
1438  .parameters {
1439  parameterTab.offsx 0
1440  parameterTab.offsy 0
1441  parameterTab.requestRedraw
1442  }
1443  .variables {
1444  variableTab.offsx 0
1445  variableTab.offsy 0
1446  variableTab.requestRedraw
1447  }
1448  .plts {
1449  plotTab.offsx 0
1450  plotTab.offsy 0
1451  plotTab.requestRedraw
1452  }
1453  .gdlys {
1454  godleyTab.offsx 0
1455  godleyTab.offsy 0
1456  godleyTab.requestRedraw
1457  }
1458  }
1459 }
1460 
1461 proc fileTypes {defaultExtension} {
1462  if {$defaultExtension==".rvl"} {
1463  return {{"Ravel" .rvl TEXT} {"Minsky" .mky TEXT} {"All Files" * TEXT}}
1464  } else {
1465  return {{"Minsky" .mky TEXT} {"Ravel" .rvl TEXT} {"All Files" * TEXT}}
1466  }
1467 }
1468 
1469 proc save {} {
1470  global fname workDir
1471  set ext [minsky.model.defaultExtension]
1472  if {![string length $fname]} {
1473  setFname [tk_getSaveFile -defaultextension $ext -initialdir $workDir \
1474  -filetypes [fileTypes $ext]]}
1475  if [string length $fname] {
1476  set workDir [file dirname $fname]
1477  eval minsky.save {$fname}
1478  file delete -- [autoBackupName]
1479  }
1480 }
1481 
1482 proc saveAs {} {
1483  global fname workDir
1484  set ext [minsky.model.defaultExtension]
1485  setFname [tk_getSaveFile -defaultextension $ext -initialdir $workDir \
1486  -filetypes [fileTypes $ext]]
1487  if [string length $fname] save
1488 }
1489 
1490 proc newSystem {} {
1491  doPushHistory 0
1492  if {[edited]} {
1493  switch [tk_messageBox -message "Save?" -type yesnocancel] {
1494  yes save
1495  no {}
1496  cancel {return -level [info level]}
1497  }
1498  }
1499  catch {reset}
1500  clearAllMapsTCL
1501  pushFlags
1503  clearHistory
1504  model.setZoom 1
1506  global fname progName
1507  set fname ""
1508  file delete [autoBackupName]
1509  wm title . "$progName: New System"
1510  popFlags
1511  doPushHistory 1
1512 }
1513 
1514 proc toggleImplicitSolver {} {
1515  global implicitSolver
1516  implicit $implicitSolver
1517 }
1518 
1519 # invokes OK or cancel button with given window, depending on current focus
1520 proc invokeOKorCancel {window} {
1521  if [string equal [focus] "$window.cancel"] {
1522  $window.cancel invoke
1523  } else {
1524  $window.ok invoke
1525  }
1526 }
1527 
1528 set rkVars {
1529  timeUnit "Time unit"
1530  stepMin "Min Step Size"
1531  stepMax "Max Step Size"
1532  nSteps "No. steps per iteration"
1533  t0 "Start time"
1534  tmax "Run until time"
1535  epsAbs "Absolute error"
1536  epsRel "Relative error"
1537  order "Solver order (1,2 or 4)"
1538 }
1539 
1540 proc deiconifyRKDataForm {} {
1541  if {![winfo exists .rkDataForm]} {
1542  global rkVarInput rkVars
1543  toplevel .rkDataForm
1544  wm resizable .rkDataForm 0 0
1545 
1546  set row 0
1547 
1548  grid [label .rkDataForm.label$row -text "Simulation parameters"] -column 1 -columnspan 999 -pady 10
1549  incr row 10
1550 
1551  foreach {var text} $rkVars {
1552  set rowdict($text) $row
1553  grid [label .rkDataForm.label$row -text $text] -column 10 -row $row -sticky e
1554  grid [entry .rkDataForm.text$row -width 20 -textvariable rkVarInput($var)] -column 20 -row $row -sticky ew
1555  incr row 10
1556  }
1557  grid [label .rkDataForm.implicitlabel -text "Implicit solver"] -column 10 -row $row -sticky e
1558  grid [checkbutton .rkDataForm.implicitcheck -variable implicitSolver -command toggleImplicitSolver] -column 20 -row $row -sticky ew
1559 
1560  set rkVarInput(initial_focus) ".rkDataForm.text$rowdict(Min Step Size)"
1561  frame .rkDataForm.buttonBar
1562  button .rkDataForm.buttonBar.ok -text OK -command {setRKparms; closeRKDataForm}
1563  button .rkDataForm.buttonBar.cancel -text cancel -command {closeRKDataForm}
1564  pack .rkDataForm.buttonBar.ok [label .rkDataForm.buttonBar.spacer -width 2] .rkDataForm.buttonBar.cancel -side left -pady 10
1565  grid .rkDataForm.buttonBar -column 1 -row 999 -columnspan 999
1566 
1567  bind .rkDataForm <Key-Return> {invokeOKorCancel .rkDataForm.buttonBar}
1568 
1569  wm title .rkDataForm "Simulation parameters"
1570  # help bindings
1571  bind .rkDataForm <F1> {help RungeKutta}
1572  global helpTopics
1573  set helpTopics(.rkDataForm) RungeKutta
1574  bind .rkDataForm <<contextMenu>> {helpContext %X %Y}
1575  } else {
1576  deiconify .rkDataForm
1577  }
1578 }
1579 
1580 proc closeRKDataForm {} {
1581  grab release .rkDataForm
1582  wm withdraw .rkDataForm
1583 }
1584 
1585 proc setRKparms {} {
1586  global rkVars rkVarInput
1587  foreach {var text} $rkVars { $var $rkVarInput($var)}
1588 }
1589 
1590 
1591 proc closePreferencesForm {} {
1592  destroy .preferencesForm
1593 }
1594 
1595 proc setPreferenceParms {} {
1596  global preferencesVars preferences preferences_input
1597 
1598  foreach var [array names preferences_input] {
1599  set preferences($var) $preferences_input($var)
1600  }
1601  defaultFont $preferences(defaultFont)
1602  multipleEquities $preferences(multipleEquities)
1604  if {$preferences(focusFollowsMouse)} {
1605  tk_focusFollowsMouse
1606  # Make tab traversal possible within a window that is given focus by only clicking on it (no focusFollowsMouse). For ticket 901.
1607  } else {
1608  set old [bind all <Enter>]
1609  set script {
1610  if {"%d" eq "NotifyAncestor" || "%d" eq "NotifyNonlinear" \
1611  || "%d" eq "NotifyInferior"} {
1612  tk::FocusOK %W
1613  }
1614  }
1615  if {$old ne ""} {
1616  bind all <Enter> "$old; $script"
1617  } else {
1618  bind all <Enter> $script
1619  }
1620  }
1621 }
1622 
1624 
1625 # context sensitive help topics associations
1626 set helpTopics(.#menubar) Menu
1627 set helpTopics(.menubar.file) File
1628 set helpTopics(.menubar.edit) Edit
1629 set helpTopics(.menubar.ops) Insert
1630 set helpTopics(.menubar.options) Options
1631 set helpTopics(.controls.rec) RecReplayButtons
1632 set helpTopics(.controls.runmode) RecReplayButtons
1633 set helpTopics(.controls.run) RunButtons
1634 set helpTopics(.controls.reset) RunButtons
1635 set helpTopics(.controls.step) RunButtons
1636 set helpTopics(.controls.simSpeed) Speedslider
1637 set helpTopics(.controls.statusbar) SimTime
1638 set helpTopics(.controls.zoomOut) ZoomButtons
1639 set helpTopics(.controls.zoomIn) ZoomButtons
1640 set helpTopics(.controls.zoomOrig) ZoomButtons
1641 set helpTopics(.controls.zoomFit) ZoomButtons
1642 # TODO - the following association interferes with canvas item context menus
1643 # set helpTopics(.wiring.canvas) DesignCanvas
1644 
1645 
1646 menu .contextHelp -tearoff 0
1647 foreach win [array names helpTopics] {
1648  bind $win <<contextMenu>> {helpContext %X %Y}
1649 }
1650 
1651 # for binding to F1
1652 proc topLevelHelp {} {
1653  helpFor [winfo pointerx .] [winfo pointery .]
1654 }
1655 
1656 # for binding to context menus
1657 proc helpContext {x y} {
1658  .contextHelp delete 0 end
1659  .contextHelp add command -label Help -command "helpFor $x $y"
1660  tk_popup .contextHelp $x $y
1661 }
1662 
1663 # implements context sensistive help
1664 proc helpFor {x y} {
1665  global helpTopics
1666  set win [winfo containing $x $y]
1667  if {$win==".wiring.canvas"} {
1668  canvasHelp
1669  } elseif [info exists helpTopics($win)] {
1670  help $helpTopics($win)
1671  } else {
1672  help Introduction
1673  }
1674 }
1675 
1676 proc canvasHelp {} {
1677  set x [get_pointer_x .wiring.canvas]
1678  set y [get_pointer_y .wiring.canvas]
1679  if [getItemAt $x $y] {
1680  help [minsky.canvas.item.classType]
1681  } elseif [getWireAt $x $y] {
1682  help wire
1683  } else {help DesignCanvas}
1684 }
1685 
1686 proc openURL {URL} {
1687  global tcl_platform
1688  if {[tk windowingsystem]=="win32"} {
1689  shellOpen $URL
1690  } elseif {$tcl_platform(os)=="Darwin"} {
1691  exec open $URL
1692  } elseif [catch {exec xdg-open $URL &}] {
1693  # try a few likely suspects
1694  foreach browser {firefox konqueror seamonkey opera} {
1695  set browserNotFound [catch {exec $browser $URL &}]
1696  if {!$browserNotFound} break
1697  }
1698  if $browserNotFound {
1699  tk_messageBox -detail "Unable to find a working web browser,
1700 please consult $URL" -type ok -icon warning
1701  }
1702  }
1703 }
1704 
1705 proc help {topic} {
1706  global minskyHome externalLabel
1707  # replace "Introduction" to framed toplevel document
1708  # TODO - see if it is possible to wrap the deep links with a framed service
1709  if {$topic=="Introduction"} {
1710  set URL "file://$minskyHome/library/help/minsky.html"
1711  } else {
1712  set URL "file://$minskyHome/library/help/minsky$externalLabel($topic)"
1713  }
1714  openURL $URL
1715 }
1716 
1717 proc aboutMinsky {} {
1718  tk_messageBox -message "
1719  Minsky [minskyVersion]\n
1720  Version used to save file [fileVersion]\n
1721  Tcl/Tk [info tclversion]\n
1722  Ravel [ravelVersion]
1723 " -detail "
1724  Minsky is FREE software, distributed under the
1725  GNU General Public License. It comes with ABSOLUTELY NO WARRANTY.
1726  See http://www.gnu.org/licenses/ for details
1727 
1728  Some icons from the Antü Plasma Suita are licensed under Creative
1729  Commons Attribution-Share Alike 3.0 Unported license
1730  (https://creativecommons.org/licenses/by-sa/3.0/deed.en).
1731 
1732  Ravel is copyright Ravelation Pty Ltd. A separate license needs to
1733  be purchased to use Ravel. See https://ravelation.hpcoders.com.au
1734 
1735 Thanks to following Minsky Unicorn sponsors:
1736  Edward McDaniel
1737  Travis Kimmel
1738  "
1739 }
1740 
1741 # delete subsidiary toplevel such as godleys and plots
1742 proc deleteSubsidiaryTopLevels {} {
1743  global globals
1744 
1745  canvas.defaultRotation 0
1746  set globals(godley_tables) {}
1747 
1748  foreach w [info commands .godley*] {destroy $w}
1749  foreach w [info commands .plot*] {destroy $w}
1750  foreach image [image names] {
1751  if [regexp ".plot.*|godleyImage.*|groupImage.*|varImage.*|opImage.*|plot_image.*" $image] {
1752  image delete $image
1753  }
1754  }
1755 }
1756 
1757 proc exit {} {
1758  # check if the model has been saved yet
1759  if {[edited]} {
1760  switch [tk_messageBox -message "Save before exiting?" -type yesnocancel] {
1761  yes save
1762  no {file delete -- [autoBackupName]}
1763  cancel {return -level [info level]}
1764  }
1765  }
1766 
1767 
1768  # if we have a valid rc file location, write out the directory of
1769  # the last file loaded
1770  global rcfile workDir backgroundColour preferences recentFiles
1771  if {$rcfile!=""} {
1772  set rc [open $rcfile w]
1773  puts $rc "set workDir $workDir"
1774  puts $rc "set canvasWidth [winfo width .wiring.canvas]"
1775  puts $rc "set canvasHeight [winfo height .wiring.canvas]"
1776  puts $rc "set backgroundColour $backgroundColour"
1777  foreach p [array names preferences] {
1778  puts $rc "set preferences($p) \{$preferences($p)\}"
1779  }
1780  puts $rc {minsky.defaultFont $preferences(defaultFont)}
1781  puts $rc "set recentFiles \{$recentFiles\}"
1782  close $rc
1783  }
1784  # why is this needed?
1785  proc bgerror x {}
1786  tcl_exit
1787 }
1788 
1789 proc setFname {name} {
1790  global fname workDir recentFiles preferences progName
1791  if [string length $name] {
1792  set fname $name
1793  set workDir [file dirname $name]
1794  if {[lsearch $recentFiles $fname]==-1} {
1795  #add to recent files list
1796  if {[llength $recentFiles]>=$preferences(nRecentFiles)} {
1797  set recentFiles [lreplace $recentFiles 0 0]
1798  }
1799  lappend recentFiles $fname
1801  }
1802  # this strange piece of merde proved the only way I could test
1803  # for the presence of a close brace
1804  if {[regexp "\}$" $fname] && ![regexp "\}$" $workDir]} {
1805  set workDir "$workDir\}"
1806  }
1807  catch {wm title . "$progName: $fname"}
1808  }
1809 }
1810 
1811 # commands to redirect to wiringGroup (via unknown mechanism)
1812 set getters {wire op constant integral data var plot godley group switchItem item items wires}
1813 foreach i $getters {
1814  foreach j [info commands $i.*] {rename $j {}}
1815 }
1816 
1817 rename unknown ecolab_unknown
1818 proc unknown {procname args} {
1819  #delegate in case a getter hasn't correctly called its get
1820  global getters
1821  if [regexp ^wiringGroup\. $procname] {
1822  # delegate to minsky (ie global group)
1823  eval [regsub ^wiringGroup $procname minsky] $args
1824  } elseif [regexp ^([join $getters |])\. $procname] {
1825  eval wiringGroup.$procname $args
1826  } else {
1827  eval ecolab_unknown $procname $args
1828  }
1829 }
1830 
1831 pushFlags
1832 
1833 if {$argc>1} {
1834  #if argv(1) has .mdl extension, it is a Vensim model file
1835  if [string match "*.mdl" $argv(1)] {
1836  catch {eval importVensim $argv(1)}
1837  minsky.model.autoLayout
1838 # minsky.canvas.requestRedraw
1839  .controls.zoomFit invoke
1840  } elseif {![string match "*.tcl" $argv(1)]} {
1841  catch {eval openNamedFile {$argv(1)}}
1842  }
1843 }
1844 
1845 proc ifDef {var} {
1846  upvar $var v
1847  if {$v!="??"} {
1848  return "-$var $v "
1849  } else {
1850  return ""
1851  }
1852 }
1853 
1854 proc addEvent {event window button height state width x y delta keysym subwindow} {
1855  global eventRecord eventRecording
1856  if {$eventRecording && [info exists eventRecord] && [llength [file channels $eventRecord]]} {
1857  set rec "event generate $window $event "
1858  foreach option {button height state width x y delta keysym} {
1859  # for some reason, the logic misses this one
1860  if {($event=="<Key>" || $event=="<KeyRelease>") && $option=="delta"} continue
1861  append rec [ifDef $option]
1862  }
1863  puts $eventRecord $rec
1864  }
1865 }
1866 
1867 proc startRecording {filename} {
1868  if {[string length $filename]>0} {
1869  minsky.startRecording $filename
1871  }
1872 }
1873 
1874 proc stopRecording {} {
1875  minsky.stopRecording
1876 }
1877 
1878 proc toggleRecording {} {
1879  global eventRecording workDir
1880  if $eventRecording {
1881  startRecording [tk_getSaveFile -filetypes {{"TCL scripts" .tcl TEXT} {"All Files" * }}\
1882  -defaultextension .tcl -initialdir $workDir]
1883  } else {
1885  }
1886 }
1887 
1888 proc checkRecordingVersion ver {
1889  if {$ver!=[minskyVersion]} {
1890  tk_messageBox -icon warning -message "Recording version $ver differs from current Minsky version [minskyVersion]" -detail "Recording may not replay correctly"
1891  }
1892 }
1893 
1894 # flag indicating we're in recording replay mode
1895 set recordingReplay 0
1896 
1897 proc replay {} {
1898  global recordingReplay eventRecordR workDir eventRecording
1899  if $eventRecording {stopRecording; set eventRecording 0}
1900  if {$recordingReplay} {
1901  # ensures consistent IDs are allocated
1902  set fname [tk_getOpenFile -filetypes {{"TCL scripts" .tcl TEXT} {"All Files" * }} \
1903  -defaultextension .tcl -initialdir $workDir]
1904  if {[string length $fname]>0} {
1905  set eventRecordR [eval open {$fname} r]
1906  newSystem
1907  if {![running]} runstop
1908  } else {
1909  if [running] {runstop}
1910  set recordingReplay 0
1911  }
1912  }
1913 }
1914 
1915 # check whether coverage analysis is required
1916 if [info exists env(MINSKY_COV)] {attachTraceProc ::}
1917 
1918 # a hook to allow code to be run after Minsky has initialised itself
1919 if {[llength [info commands afterMinskyStarted]]>0} {
1920  afterMinskyStarted
1921 }
1922 
1923 setGodleyDisplayValue $preferences(godleyDisplay) $preferences(godleyDisplayStyle)
1924 disableEventProcessing
1925 popFlags
1926 #pushHistory
1927