From 2c0d129dc28c54965b0985f8aec348b8b4006638 Mon Sep 17 00:00:00 2001
From: Ivica Ico Bukvic <ico@vt.edu>
Date: Mon, 16 Sep 2013 21:34:58 -0400
Subject: [PATCH] *added adapted backport of recent files feature *code
 clean-up *brought the rest of disabled tcl files up-to-date with the 0.45
 branch for possible future merge

---
 pd/src/makefile.in         |   3 +-
 pd/src/pd.tk               |  64 +++-
 pd/src/pd_guiprefs.tcl     | 250 ++++++++++++++++
 pd/src/pd_menus.tcl        | 473 ------------------------------
 pd/src/pd_menus_SHORT.tcl  |  90 ++++++
 pd/src/pkgIndex.tcl        |   2 +
 pd/tcl/AppMain.tcl         |   9 -
 pd/tcl/Makefile.am         |  16 +
 pd/tcl/apple_events.tcl    |  50 ++--
 pd/tcl/dialog_array.tcl    |  21 +-
 pd/tcl/dialog_audio.tcl    | 251 +++++++++-------
 pd/tcl/dialog_canvas.tcl   |  26 +-
 pd/tcl/dialog_data.tcl     |  53 ++++
 pd/tcl/dialog_find.tcl     | 145 ++++++---
 pd/tcl/dialog_font.tcl     |  61 ++--
 pd/tcl/dialog_gatom.tcl    |  45 +--
 pd/tcl/dialog_iemgui.tcl   | 107 ++++---
 pd/tcl/dialog_message.tcl  |  85 ++++++
 pd/tcl/dialog_midi.tcl     |  15 +-
 pd/tcl/dialog_path.tcl     |  70 +++++
 pd/tcl/dialog_startup.tcl  |  96 ++++++
 pd/tcl/helpbrowser.tcl     | 272 +++++++++++++++++
 pd/tcl/opt_parser.tcl      |  40 ++-
 pd/tcl/pd-gui.tcl          | 584 ++++++++++++++++++++++++++-----------
 pd/tcl/pd.ico              | Bin 0 -> 25214 bytes
 pd/tcl/pd_bindings.tcl     | 305 +++++++++----------
 pd/tcl/pd_connect.tcl      |  90 +++---
 pd/tcl/pd_guiprefs.tcl     | 249 ++++++++++++++++
 pd/tcl/pd_menucommands.tcl | 227 ++++++++------
 pd/tcl/pd_menus.tcl        | 482 +++++++++++++++++++-----------
 pd/tcl/pd_menus.tcl~       | 473 ------------------------------
 pd/tcl/pdtk_canvas.tcl     | 355 +++++++++++++++++-----
 pd/tcl/pdtk_text.tcl       |  56 +++-
 pd/tcl/pdtk_textwindow.tcl | 103 +++++++
 pd/tcl/pdwindow.tcl        | 403 +++++++++++++++++++++++--
 pd/tcl/pkgIndex.tcl        |   9 +
 pd/tcl/scrollbox.tcl       | 191 ++++++++++++
 pd/tcl/scrollboxwindow.tcl |  94 ++++++
 pd/tcl/wheredoesthisgo.tcl | 124 ++++----
 39 files changed, 3918 insertions(+), 2071 deletions(-)
 create mode 100644 pd/src/pd_guiprefs.tcl
 delete mode 100644 pd/src/pd_menus.tcl
 create mode 100644 pd/src/pd_menus_SHORT.tcl
 create mode 100644 pd/tcl/Makefile.am
 create mode 100644 pd/tcl/dialog_data.tcl
 create mode 100644 pd/tcl/dialog_message.tcl
 create mode 100644 pd/tcl/dialog_path.tcl
 create mode 100644 pd/tcl/dialog_startup.tcl
 create mode 100644 pd/tcl/helpbrowser.tcl
 create mode 100755 pd/tcl/pd.ico
 create mode 100644 pd/tcl/pd_guiprefs.tcl
 delete mode 100644 pd/tcl/pd_menus.tcl~
 create mode 100644 pd/tcl/pdtk_textwindow.tcl
 create mode 100644 pd/tcl/scrollbox.tcl
 create mode 100644 pd/tcl/scrollboxwindow.tcl

diff --git a/pd/src/makefile.in b/pd/src/makefile.in
index 853c6a1e8..a0dc46c73 100644
--- a/pd/src/makefile.in
+++ b/pd/src/makefile.in
@@ -207,8 +207,7 @@ install: all tkpath tkdnd
 	install $(BIN_DIR)/$(GUINAME) $(DESTDIR)$(libpdbindir)/$(GUINAME)
 	install $(BIN_DIR)/pd-watchdog $(DESTDIR)$(libpdbindir)/pd-watchdog
 	install -m644 pd.tk $(DESTDIR)$(libpdbindir)/pd.tk
-	install -m644 pkgIndex.tcl $(DESTDIR)$(libpdbindir)/pkgIndex.tcl
-	install -m644 helpbrowser.tcl $(DESTDIR)$(libpdbindir)/helpbrowser.tcl
+	install -m644 *.tcl $(DESTDIR)$(libpdbindir)/
 	install -m644 ../tkpath/library/tkpath.tcl $(DESTDIR)$(libpdbindir)/tkpath.tcl
 	install -m644 ../tkpath/libtkpath*so $(DESTDIR)$(libpdbindir)/
 	install -m644 ../tkdnd/library/tkdnd.tcl $(DESTDIR)$(libpdbindir)/tkdnd.tcl
diff --git a/pd/src/pd.tk b/pd/src/pd.tk
index a422e10db..f097887aa 100644
--- a/pd/src/pd.tk
+++ b/pd/src/pd.tk
@@ -619,6 +619,12 @@ set help_top_directory $pd_guidir/doc
 #==============================================================================#
 # pd-gui-rewrite-0.43 help browser backport
 
+switch -- $pd_nt {
+    0  { set ::windowingsystem "x11" }
+    1  { set ::windowingsystem "win32" }
+    2  { set ::windowingsystem "aqua" }
+}
+
 # Pd's packages are stored in the same directory as the main script (pd-gui.tcl)
 set auto_path [linsert $auto_path 0 [file dirname [info script]]]
 # the pd-gui-rewrite-0.43 help browser is backported to Pd-extended 0.42
@@ -626,6 +632,15 @@ package require helpbrowser
 package require msgcat
 package require tkpng
 package require tkdnd
+package require pd_guiprefs
+package require pd_menus
+
+namespace import ::pd_guiprefs::init
+namespace import ::pd_guiprefs::update_recentfiles
+namespace import ::pd_guiprefs::write_recentfiles
+
+set total_recentfiles 5
+::pd_guiprefs::init
 
 # official GNU gettext msgcat shortcut
 proc _ {s} {return [::msgcat::mc $s]}
@@ -659,7 +674,7 @@ ttk::style configure IOErrorOff.TButton -background lightgrey
 ttk::style map IOErrorOff.TButton -background [list active $colors(-frame)]
 
 match_linux_wm [list menu .mbar -relief flat]
-match_linux_wm [list menu .mbar.file -tearoff $pd_tearoff]
+match_linux_wm [list menu .mbar.file -tearoff $pd_tearoff -postcommand [concat pdtk_fixfilemenu .mbar.file]]
 .mbar add cascade -label File -menu .mbar.file
 match_linux_wm [list menu .mbar.edit -tearoff $pd_tearoff]
 .mbar add cascade -label Edit -menu .mbar.edit
@@ -1041,6 +1056,10 @@ proc pdtk_canvas_menuclose {window reply} {
 	}
 }
 
+proc pdtk_fixfilemenu {name} {
+	::pd_menus::update_recentfiles_menu $name false
+}
+
 set menu_windowlist {} 
 
 proc pdtk_fixwindowmenu {} {
@@ -1183,6 +1202,7 @@ proc open_file {filename} {
     if {[regexp -nocase -- "\.(pd|pat|mxt)$" $basename]} {
         pd "pd open [pdtk_enquote $basename] [pdtk_enquote $directory] ;"
         set pd_opendir $directory
+        ::pd_guiprefs::update_recentfiles $filename 1
     }
 }
 
@@ -1543,13 +1563,11 @@ proc menu_addstd {mbar} {
 .mbar.file add command -label Open -command {menu_open} \
     -accelerator [accel_munge "Ctrl+o"]
 match_linux_wm [list .mbar.file add separator]
-.mbar.file add command -label Close -accelerator [accel_munge "Ctrl+w"] \
-	-state disabled
 .mbar.file add command -label Save -accelerator [accel_munge "Ctrl+s"] \
 	-state disabled
 .mbar.file add command -label "Save as..." -accelerator [accel_munge "Ctrl+S"] \
 	-state disabled
-match_linux_wm [list .mbar.file add  separator]
+match_linux_wm [list .mbar.file add separator]
 if {$pd_nt != 2} {
 	.mbar.file add command -label "Message..." -command {menu_send} \
 		-accelerator [accel_munge "Ctrl+m"]
@@ -1570,11 +1588,21 @@ if {$pd_nt != 2} {
 match_linux_wm [list .mbar.file add  separator]
 .mbar.file add command -label "Print..." -accelerator [accel_munge "Ctrl+p"] \
 	-state disabled
-if {$pd_nt != 2} {
-# Mac OS X doesn't put Quit on the File menu
+
+# update recent files
+match_linux_wm [list .mbar.file add separator]
+#if {[llength $::recentfiles_list] > 0} {
+#    ::pd_menus::update_recentfiles_menu .mbar.file false
+#}
+
 match_linux_wm [list .mbar.file add  separator]
-.mbar.file add command -label Quit -command {menu_quit} \
-    -accelerator [accel_munge "Ctrl+q"]
+.mbar.file add command -label Close -accelerator [accel_munge "Ctrl+w"] \
+	-state disabled
+
+if {$pd_nt != 2} {
+	# Mac OS X doesn't put Quit on the File menu
+	.mbar.file add command -label Quit -command {menu_quit} \
+	    -accelerator [accel_munge "Ctrl+q"]
 }
 
 #################### the "Edit" menu for the Pd window ##############
@@ -2765,7 +2793,7 @@ proc pdtk_canvas_new {name width height geometry editable} {
     # For the main window, they are created on load, at the 
     # top of this file.
     match_linux_wm [list menu $name.m -relief flat]
-    match_linux_wm [list menu $name.m.file -tearoff $pd_tearoff]
+    match_linux_wm [list menu $name.m.file  -postcommand [concat pdtk_fixfilemenu $name.m.file] -tearoff $pd_tearoff]
     $name.m add cascade -label File -menu $name.m.file
 
     $name.m.file add command -label New -command {menu_new} \
@@ -2779,9 +2807,6 @@ proc pdtk_canvas_new {name width height geometry editable} {
 	}
 
     match_linux_wm [list $name.m.file add separator]
-    $name.m.file add command -label Close \
-        -command [concat menu_close $name] \
-        -accelerator [accel_munge "Ctrl+w"]
 
     $name.m.file add command -label Save -command [concat menu_save $name] \
         -accelerator [accel_munge "Ctrl+s"]
@@ -2814,9 +2839,20 @@ proc pdtk_canvas_new {name width height geometry editable} {
 		$name.m.file add command -label "Print..." -command [concat menu_print $name] \
 		    -accelerator [accel_munge "Ctrl+p"]
 	}
+    # update recent files
+    match_linux_wm [list $name.m.file add separator]
+    #match_linux_wm [list $name.m.file add separator]
+    #if {[llength $::recentfiles_list] > 0} {
+    #    ::pd_menus::update_recentfiles_menu $name.m.file false
+    #}
+
+	match_linux_wm [list $name.m.file add separator]
+    $name.m.file add command -label Close \
+        -command [concat menu_close $name] \
+        -accelerator [accel_munge "Ctrl+w"]
+
 	if {$pd_nt != 2} {
 		# Mac OS X doesn't put Quit on the File menu
-		match_linux_wm [list $name.m.file add separator]
 		$name.m.file add command -label Quit -command {menu_quit} \
 			-accelerator [accel_munge "Ctrl+q"]
 	}
@@ -4722,6 +4758,8 @@ proc pdtk_canvas_saveas {name initfile initdir} {
             [pdtk_enquote $directory] \;]
     #       pd [concat $name savetofile $basename $directory \;]
     set untitled_directory $directory
+    # add to recentfiles
+    ::pd_guiprefs::update_recentfiles $filename 1
 }
 
 # zooming (a subset of dofont)
diff --git a/pd/src/pd_guiprefs.tcl b/pd/src/pd_guiprefs.tcl
new file mode 100644
index 000000000..f97d2b45e
--- /dev/null
+++ b/pd/src/pd_guiprefs.tcl
@@ -0,0 +1,250 @@
+#
+# Copyright (c) 1997-2009 Miller Puckette.
+# Copyright (c) 2011 Yvan Volochine.
+#(c) 2008 WordTech Communications LLC. License: standard Tcl license, http://www.tcl.tk/software/tcltk/license.html
+
+package provide pd_guiprefs 0.1
+
+
+namespace eval ::pd_guiprefs:: {
+    namespace export init
+    namespace export write_recentfiles
+    namespace export update_recentfiles
+}
+
+# FIXME should these be globals ?
+set ::recentfiles_key ""
+set ::recentfiles_domain ""
+
+
+#################################################################
+# global procedures
+#################################################################
+# ------------------------------------------------------------------------------
+# init preferences
+#
+proc ::pd_guiprefs::init {} {
+    switch -- $::windowingsystem {
+        "aqua"  { init_aqua }
+        "win32" { init_win }
+        "x11"   { init_x11 }
+    }
+    # assign gui preferences
+    # osx special case for arrays
+    set arr [expr { $::windowingsystem eq "aqua" }]
+    set ::recentfiles_list ""
+    catch {set ::recentfiles_list [get_config $::recentfiles_domain \
+        $::recentfiles_key $arr]}
+}
+
+proc ::pd_guiprefs::init_aqua {} {
+    # osx has a "Open Recent" menu with 10 recent files (others have 5 inlined)
+    set ::recentfiles_domain org.puredata
+    set ::recentfiles_key "NSRecentDocuments"
+    set ::total_recentfiles 10
+}
+
+proc ::pd_guiprefs::init_win {} {
+    # windows uses registry
+    set ::recentfiles_domain "HKEY_CURRENT_USER\\Software\\Pd-L2Ork"
+    set ::recentfiles_key "RecentDocs"
+}
+
+proc ::pd_guiprefs::init_x11 {} {
+    # linux uses ~/.config/pure-data dir
+    set ::recentfiles_domain "~/.config/pd-l2ork"
+    set ::recentfiles_key "recentfiles.conf"
+    prepare_configdir
+}
+
+# ------------------------------------------------------------------------------
+# write recent files
+#
+proc ::pd_guiprefs::write_recentfiles {} {
+    write_config $::recentfiles_list $::recentfiles_domain $::recentfiles_key true
+}
+
+# ------------------------------------------------------------------------------
+# this is called when opening a document (wheredoesthisshouldgo.tcl)
+#
+proc ::pd_guiprefs::update_recentfiles {afile save} {
+    # remove duplicates first
+    set index [lsearch -exact $::recentfiles_list $afile]
+    set ::recentfiles_list [lreplace $::recentfiles_list $index $index]
+    # insert new one in the beginning and crop the list
+    set ::recentfiles_list [linsert $::recentfiles_list 0 $afile]
+    set ::recentfiles_list [lrange $::recentfiles_list 0 $::total_recentfiles]
+    #::pd_menus::update_recentfiles_menu .mbar.file $save
+    ::pd_guiprefs::write_recentfiles
+}
+
+#################################################################
+# main read/write procedures
+#################################################################
+
+# ------------------------------------------------------------------------------
+# get configs from a file or the registry
+#
+proc ::pd_guiprefs::get_config {adomain {akey} {arr}} {
+    switch -- $::windowingsystem {
+        "aqua"  { set conf [get_config_aqua $adomain $akey $arr] }
+        "win32" { set conf [get_config_win $adomain $akey $arr] }
+        "x11"   { set conf [get_config_x11 $adomain $akey $arr] }
+    }
+    return $conf
+}
+
+# ------------------------------------------------------------------------------
+# write configs to a file or to the registry
+# $arr is true if the data needs to be written in an array
+#
+proc ::pd_guiprefs::write_config {data {adomain} {akey} {arr false}} {
+    switch -- $::windowingsystem {
+        "aqua"  { write_config_aqua $data $adomain $akey $arr }
+        "win32" { write_config_win $data $adomain $akey $arr }
+        "x11"   { write_config_x11 $data $adomain $akey }
+    }
+}
+
+#################################################################
+# os specific procedures
+#################################################################
+
+# ------------------------------------------------------------------------------
+# osx: read a plist file
+#
+proc ::pd_guiprefs::get_config_aqua {adomain {akey} {arr false}} {
+    if {![catch {exec defaults read $adomain $akey} conf]} {
+        if {$arr} {
+            set conf [plist_array_to_tcl_list $conf]
+        }
+    } else {
+        # initialize NSRecentDocuments with an empty array
+        exec defaults write $adomain $akey -array
+        set conf {}
+    }
+    return $conf
+}
+
+# ------------------------------------------------------------------------------
+# win: read in the registry
+#
+proc ::pd_guiprefs::get_config_win {adomain {akey} {arr false}} {
+    package require registry
+    if {![catch {registry get $adomain $akey} conf]} {
+        return [expr {$conf}]
+    } else {
+        return {}
+    }
+}
+
+# ------------------------------------------------------------------------------
+# linux: read a config file and return its lines splitted.
+#
+proc ::pd_guiprefs::get_config_x11 {adomain {akey} {arr false}} {
+    set filename [file join $adomain $akey]
+    set conf {}
+    if {
+        [file exists $filename] == 1
+        && [file readable $filename]
+    } {
+        set fl [open $filename r]
+        while {[gets $fl line] >= 0} {
+           lappend conf $line
+        }
+        close $fl
+    }
+    return $conf
+}
+
+# ------------------------------------------------------------------------------
+# osx: write configs to plist file
+# if $arr is true, we write an array
+#
+proc ::pd_guiprefs::write_config_aqua {data {adomain} {akey} {arr false}} {
+    # FIXME empty and write again so we don't loose the order
+    if {[catch {exec defaults write $adomain $akey -array} errorMsg]} {
+        ::pdwindow::error "write_config_aqua $akey: $errorMsg"
+    }
+    if {$arr} {
+        foreach filepath $data {
+            set escaped [escape_for_plist $filepath]
+            exec defaults write $adomain $akey -array-add "$escaped"
+        }
+    } else {
+        set escaped [escape_for_plist $data]
+        exec defaults write $adomain $akey '$escaped'
+    }
+}
+
+# ------------------------------------------------------------------------------
+# win: write configs to registry
+# if $arr is true, we write an array
+#
+proc ::pd_guiprefs::write_config_win {data {adomain} {akey} {arr false}} {
+    package require registry
+    # FIXME: ugly
+    if {$arr} {
+        if {[catch {registry set $adomain $akey $data multi_sz} errorMsg]} {
+            ::pdwindow::error "write_config_win $data $akey: $errorMsg"
+        }
+    } else {
+        if {[catch {registry set $adomain $akey $data sz} errorMsg]} {
+            ::pdwindow::error "write_config_win $data $akey: $errorMsg"
+        }
+    }
+}
+
+# ------------------------------------------------------------------------------
+# linux: write configs to USER_APP_CONFIG_DIR
+#
+proc ::pd_guiprefs::write_config_x11 {data {adomain} {akey}} {
+    # right now I (yvan) assume that data are just \n separated, i.e. no keys
+    set data [join $data "\n"]
+    set filename [file join $adomain $akey]
+    if {[catch {set fl [open $filename w]} errorMsg]} {
+        ::pdwindow::error "write_config_x11 $data $akey: $errorMsg"
+    } else {
+        puts -nonewline $fl $data
+        close $fl
+    }
+}
+
+#################################################################
+# utils
+#################################################################
+
+# ------------------------------------------------------------------------------
+# linux only! : look for pd config directory and create it if needed
+#
+proc ::pd_guiprefs::prepare_configdir {} {
+    if {[file isdirectory $::recentfiles_domain] != 1} {
+        file mkdir $::recentfiles_domain
+        ::pdwindow::debug "$::recentfiles_domain was created.\n"
+    }
+}
+
+# ------------------------------------------------------------------------------
+# osx: handles arrays in plist files (thanks hc)
+#
+proc ::pd_guiprefs::plist_array_to_tcl_list {arr} {
+    set result {}
+    set filelist $arr
+    regsub -all -- {("?),\s+("?)} $filelist {\1 \2} filelist
+    regsub -all -- {\n} $filelist {} filelist
+    regsub -all -- {^\(} $filelist {} filelist
+    regsub -all -- {\)$} $filelist {} filelist
+    regsub -line -- {^'(.*)'$} $filelist {\1} filelist
+
+    foreach file $filelist {
+        set filename [regsub -- {,$} $file {}]
+        lappend result $filename
+    }
+    return $result
+}
+
+# the Mac OS X 'defaults' command uses single quotes to quote things,
+# so they need to be escaped
+proc ::pd_guiprefs::escape_for_plist {str} {
+    return [regsub -all -- {'} $str {\\'}]
+}
diff --git a/pd/src/pd_menus.tcl b/pd/src/pd_menus.tcl
deleted file mode 100644
index d9b459a92..000000000
--- a/pd/src/pd_menus.tcl
+++ /dev/null
@@ -1,473 +0,0 @@
-# Copyright (c) 1997-2009 Miller Puckette.
-#(c) 2008 WordTech Communications LLC. License: standard Tcl license, http://www.tcl.tk/software/tcltk/license.html
-
-package provide pd_menus 0.1
-
-package require pd_menucommands
-package require Tk
-#package require tile
-## replace Tk widgets with Ttk widgets on 8.5
-#namespace import -force ttk::*
-
-# TODO figure out Undo/Redo/Cut/Copy/Paste state changes for menus
-# TODO figure out parent window/window list for Window menu
-# TODO what is the Tcl package constructor or init()?
-# TODO $::pd_menus::menubar or .menubar globally?
-
-# since there is one menubar that is used for all windows, the menu -commands
-# use {} quotes so that $::focused_window is interpreted when the menu item
-# is called, not when the command is mapped to the menu item.  This is the
-# opposite of the 'bind' commands in pd_bindings.tcl
-    
-
-# ------------------------------------------------------------------------------
-# global variables
-
-# TODO this should properly be inside the pd_menus namespace, now it is global
-namespace import ::pd_menucommands::* 
-
-namespace eval ::pd_menus:: {
-    variable accelerator
-    variable menubar ".menubar"
-    variable current_toplevel ".pdwindow"
-    
-    namespace export create_menubar
-    namespace export configure_for_pdwindow
-    namespace export configure_for_canvas
-    namespace export configure_for_dialog
-
-    # turn off tearoff menus globally
-    option add *tearOff 0
-}
-
-# ------------------------------------------------------------------------------
-# 
-proc ::pd_menus::create_menubar {} {
-    variable accelerator
-    variable menubar
-    if {$::windowingsystem eq "aqua"} {
-        set accelerator "Cmd"
-    } else {
-        set accelerator "Ctrl"
-    }
-    menu $menubar
-    set menulist "file edit put find media window help"
-    if { $::windowingsystem eq "aqua" } {create_apple_menu $menubar}
-    # FIXME why does the following (if uncommented) kill my menubar?
-    # if { $::windowingsystem eq "win32" } {create_system_menu $menubar}
-    foreach mymenu $menulist {    
-        menu $menubar.$mymenu
-        $menubar add cascade -label [_ [string totitle $mymenu]] \
-            -menu $menubar.$mymenu
-        [format build_%s_menu $mymenu] $menubar.$mymenu .
-        if {$::windowingsystem eq "win32"} {
-            # fix menu font size on Windows with tk scaling = 1
-            $menubar.$mymenu configure -font menufont
-        }
-    }
-}
-
-proc ::pd_menus::configure_for_pdwindow {} {
-    variable menubar
-    # these are meaningless for the Pd window, so disable them
-    set file_items_to_disable {"Save" "Save As..." "Print..." "Close"}
-    foreach menuitem $file_items_to_disable {
-        $menubar.file entryconfigure [_ $menuitem] -state disabled
-    }
-    set edit_items_to_disable {"Undo" "Redo" "Duplicate" "Tidy Up" "Edit Mode"}
-    foreach menuitem $edit_items_to_disable {
-        $menubar.edit entryconfigure [_ $menuitem] -state disabled
-    }
-    # disable everything on the Put menu
-    for {set i 0} {$i <= [$menubar.put index end]} {incr i} {
-        # catch errors that happen when trying to disable separators
-        catch {$menubar.put entryconfigure $i -state disabled }
-    }
-}
-
-proc ::pd_menus::configure_for_canvas {mytoplevel} {
-    variable menubar
-    set file_items_to_disable {"Save" "Save As..." "Print..." "Close"}
-    foreach menuitem $file_items_to_disable {
-        $menubar.file entryconfigure [_ $menuitem] -state normal
-    }
-    set edit_items_to_disable {"Undo" "Redo" "Duplicate" "Tidy Up" "Edit Mode"}
-    foreach menuitem $edit_items_to_disable {
-        $menubar.edit entryconfigure [_ $menuitem] -state normal
-    }
-    for {set i 0} {$i <= [$menubar.put index end]} {incr i} {
-        # catch errors that happen when trying to disable separators
-        catch {$menubar.put entryconfigure $i -state normal }
-    }
-    # TODO set "Edit Mode" state using editmode($mytoplevel)
-}
-
-proc ::pd_menus::configure_for_dialog {mytoplevel} {
-    variable menubar
-    # these are meaningless for the dialog panels, so disable them
-    set file_items_to_disable {"Save" "Save As..." "Print..." "Close"}
-    foreach menuitem $file_items_to_disable {
-        $menubar.file entryconfigure [_ $menuitem] -state disabled
-    }
-    set edit_items_to_disable {"Undo" "Redo" "Duplicate" "Tidy Up" "Edit Mode"}
-    foreach menuitem $edit_items_to_disable {
-        $menubar.edit entryconfigure [_ $menuitem] -state disabled
-    }
-    # disable everything on the Put menu
-    for {set i 0} {$i <= [$menubar.put index end]} {incr i} {
-        # catch errors that happen when trying to disable separators
-        catch {$menubar.put entryconfigure $i -state disabled }
-    }
-}
-
-
-# ------------------------------------------------------------------------------
-# menu building functions
-proc ::pd_menus::build_file_menu {mymenu mytoplevel} {
-    [format build_file_menu_%s $::windowingsystem] $mymenu
-    $mymenu entryconfigure [_ "New"]        -command {menu_new}
-    $mymenu entryconfigure [_ "Open"]       -command {menu_open}
-    $mymenu entryconfigure [_ "Save"]       -command {pdsend "$::focused_window menusave"}
-    $mymenu entryconfigure [_ "Save As..."] -command {pdsend "$::focused_window menusaveas"}
-    #$mymenu entryconfigure [_ "Revert*"]    -command {menu_revert $current_toplevel}
-    $mymenu entryconfigure [_ "Close"]      -command {pdsend "$::focused_window menuclose 0"}
-    $mymenu entryconfigure [_ "Message"]    -command {menu_message_dialog}
-    $mymenu entryconfigure [_ "Print..."]   -command {menu_print $::focused_window}
-}
-
-proc ::pd_menus::build_edit_menu {mymenu mytoplevel} {
-    variable accelerator
-    $mymenu add command -label [_ "Undo"]       -accelerator "$accelerator+Z" \
-        -command {menu_undo $::focused_window}
-    $mymenu add command -label [_ "Redo"]       -accelerator "Shift+$accelerator+Z" \
-        -command {menu_redo $::focused_window}
-    $mymenu add  separator
-    $mymenu add command -label [_ "Cut"]        -accelerator "$accelerator+X" \
-        -command {pdsend "$::focused_window cut"}
-    $mymenu add command -label [_ "Copy"]       -accelerator "$accelerator+C" \
-        -command {pdsend "$::focused_window copy"}
-    $mymenu add command -label [_ "Paste"]      -accelerator "$accelerator+V" \
-        -command {pdsend "$::focused_window paste"}
-    $mymenu add command -label [_ "Duplicate"]  -accelerator "$accelerator+D" \
-        -command {pdsend "$::focused_window duplicate"}
-    $mymenu add command -label [_ "Select All"] -accelerator "$accelerator+A" \
-        -command {pdsend "$::focused_window selectall"}
-    $mymenu add  separator
-    if {$::windowingsystem eq "aqua"} {
-        $mymenu add command -label [_ "Text Editor"] \
-            -command {menu_texteditor $::focused_window}
-        $mymenu add command -label [_ "Font"]  -accelerator "$accelerator+T" \
-            -command {menu_font_dialog $::focused_window}
-    } else {
-        $mymenu add command -label [_ "Text Editor"] -accelerator "$accelerator+T"\
-            -command {menu_texteditor $::focused_window}
-        $mymenu add command -label [_ "Font"] \
-            -command {menu_font_dialog $::focused_window}
-    }
-    $mymenu add command -label [_ "Tidy Up"] \
-        -command {pdsend "$::focused_window tidy"}
-    $mymenu add command -label [_ "Toggle Console"] -accelerator "Shift+$accelerator+R" \
-        -command {.controls.switches.console invoke}
-    $mymenu add command -label [_ "Clear Console"] -accelerator "Shift+$accelerator+L" \
-        -command {menu_clear_console}
-    $mymenu add  separator
-	$mymenu add checkbutton -label [_ "Edit mode"] \
-		-accelerator "$accelerator+E" -variable ::editmode_button \
-        -command {menu_editmode $::editmode_button}
-		-command {pdsend "$::focused_window editmode 0"}
-    #if { ! [catch {console hide}]} { 
-    # TODO set up menu item to show/hide the Tcl/Tk console, if it available
-    #}
-
-    if {$::windowingsystem ne "aqua"} {
-        $mymenu add  separator
-        $mymenu add command -label [_ "Preferences"] \
-            -command {menu_preferences_dialog}
-    }
-}
-
-proc ::pd_menus::build_put_menu {mymenu mytoplevel} {
-    variable accelerator
-    $mymenu add command -label [_ "Object"] -accelerator "$accelerator+1" \
-        -command {pdsend "$::focused_window obj 0"} 
-    $mymenu add command -label [_ "Message"] -accelerator "$accelerator+2" \
-        -command {pdsend "$::focused_window msg 0"}
-    $mymenu add command -label [_ "Number"] -accelerator "$accelerator+3" \
-        -command {pdsend "$::focused_window floatatom  0"}
-    $mymenu add command -label [_ "Symbol"] -accelerator "$accelerator+4" \
-        -command {pdsend "$::focused_window symbolatom  0"}
-    $mymenu add command -label [_ "Comment"] -accelerator "$accelerator+5" \
-        -command {pdsend "$::focused_window text  0"}
-    $mymenu add  separator
-    $mymenu add command -label [_ "Bang"]    -accelerator "Shift+$accelerator+B" \
-        -command {pdsend "$::focused_window bng  0"}
-    $mymenu add command -label [_ "Toggle"]  -accelerator "Shift+$accelerator+T" \
-        -command {pdsend "$::focused_window toggle  0"}
-    $mymenu add command -label [_ "Number2"] -accelerator "Shift+$accelerator+N" \
-        -command {pdsend "$::focused_window numbox  0"}
-    $mymenu add command -label [_ "Vslider"] -accelerator "Shift+$accelerator+V" \
-        -command {pdsend "$::focused_window vslider  0"}
-    $mymenu add command -label [_ "Hslider"] -accelerator "Shift+$accelerator+H" \
-        -command {pdsend "$::focused_window hslider  0"}
-    $mymenu add command -label [_ "Vradio"]  -accelerator "Shift+$accelerator+D" \
-        -command {pdsend "$::focused_window vradio  0"}
-    $mymenu add command -label [_ "Hradio"]  -accelerator "Shift+$accelerator+I" \
-        -command {pdsend "$::focused_window hradio  0"}
-    $mymenu add command -label [_ "VU Meter"] -accelerator "Shift+$accelerator+U"\
-        -command {pdsend "$::focused_window vumeter  0"}
-    $mymenu add command -label [_ "Canvas"]  -accelerator "Shift+$accelerator+C" \
-        -command {pdsend "$::focused_window mycnv  0"}
-    $mymenu add  separator
-    $mymenu add command -label [_ "Graph"] -command {pdsend "$::focused_window graph"} 
-    $mymenu add command -label [_ "Array"] -command {pdsend "$::focused_window menuarray"}
-}
-
-proc ::pd_menus::build_find_menu {mymenu mytoplevel} {
-    variable accelerator
-    $mymenu add command -label [_ "Find..."]    -accelerator "$accelerator+F" \
-        -command {::dialog_find::menu_find_dialog $::focused_window}
-    $mymenu add command -label [_ "Find Again"] -accelerator "$accelerator+G" \
-        -command {pdsend "$::focused_window findagain"}
-    $mymenu add command -label [_ "Find Last Error"] \
-        -command {pdsend "$::focused_window finderror"} 
-}
-
-proc ::pd_menus::build_media_menu {mymenu mytoplevel} {
-    variable accelerator
-    $mymenu add radiobutton -label [_ "DSP On"] -accelerator "$accelerator+/" \
-        -variable ::dsp -value 1 -command {pdsend "pd dsp 1"}
-    $mymenu add radiobutton -label [_ "DSP Off"] -accelerator "$accelerator+." \
-        -variable ::dsp -value 0 -command {pdsend "pd dsp 0"}
-    $mymenu add  separator
-
-    set audioapi_list_length [llength $::audioapi_list]
-    for {set x 0} {$x<$audioapi_list_length} {incr x} {
-        # pdtk_post "audio [lindex [lindex $::audioapi_list $x] 0]"
-        $mymenu add radiobutton -label [lindex [lindex $::audioapi_list $x] 0] \
-            -command {menu_audio 0} -variable ::pd_whichapi \
-            -value [lindex [lindex $::audioapi_list $x] 1]\
-            -command {pdsend "pd audio-setapi $::pd_whichapi"}
-    }
-    if {$audioapi_list_length > 0} {$mymenu add separator}
-
-    set midiapi_list_length [llength $::midiapi_list]
-    for {set x 0} {$x<$midiapi_list_length} {incr x} {
-        # pdtk_post "midi [lindex [lindex $::midiapi_list $x] 0]"
-        $mymenu add radiobutton -label [lindex [lindex $::midiapi_list $x] 0] \
-            -command {menu_midi 0} -variable ::pd_whichmidiapi \
-            -value [lindex [lindex $::midiapi_list $x] 1]\
-            -command {pdsend "pd midi-setapi $::pd_whichmidiapi"}
-    }
-    if {$midiapi_list_length > 0} {$mymenu add separator}
-
-    if {$::windowingsystem ne "aqua"} {
-        $mymenu add command -label [_ "Audio settings..."] \
-            -command {pdsend "pd audio-properties"}
-        $mymenu add command -label [_ "MIDI settings..."] \
-            -command {pdsend "pd midi-properties"} 
-        $mymenu add  separator
-    }
-    $mymenu add command -label [_ "Test Audio and MIDI..."] \
-        -command {menu_doc_open doc/7.stuff/tools testtone.pd} 
-    $mymenu add command -label [_ "Load Meter"] \
-        -command {menu_doc_open doc/7.stuff/tools load-meter.pd} 
-}
-
-proc ::pd_menus::build_window_menu {mymenu mytoplevel} {
-    variable accelerator
-    if {$::windowingsystem eq "aqua"} {
-        $mymenu add command -label [_ "Minimize"] -command {menu_minimize .} \
-            -accelerator "$accelerator+M"
-        $mymenu add command -label [_ "Zoom"] -command {menu_zoom .}
-        $mymenu add  separator
-    }
-    $mymenu add command -label [_ "Parent Window"] \
-        -command {pdsend "$::focused_window findparent"}
-    $mymenu add command -label [_ "Pd window"] -command {menu_raise_pdwindow} \
-        -accelerator "$accelerator+R"
-    $mymenu add  separator
-    if {$::windowingsystem eq "aqua"} {
-        $mymenu add command -label [_ "Bring All to Front"] \
-            -command {menu_bringalltofront}
-        $mymenu add  separator
-    }
-}
-
-proc ::pd_menus::build_help_menu {mymenu mytoplevel} {
-    if {$::windowingsystem ne "aqua"} {
-        $mymenu add command -label [_ "About Pd"] \
-            -command {menu_doc_open doc/1.manual 1.introduction.txt} 
-    }
-    $mymenu add command -label [_ "HTML Manual..."] \
-        -command {menu_doc_open doc/1.manual index.htm}
-    $mymenu add command -label [_ "Browser..."] \
-        -command {placeholder menu_helpbrowser \$help_top_directory} 
-}
-
-# ------------------------------------------------------------------------------
-# update the menu entries for opening recent files
-proc ::pd_menus::update_recentfiles_menu {} {
-    variable menubar
-    switch -- $::windowingsystem {
-        "aqua" {::pd_menus::update_openrecent_menu_aqua .openrecent}
-        "win32" {update_recentfiles_on_menu $menubar.file}
-        "x11" {update_recentfiles_on_menu $menubar.file}
-    }
-}
-
-proc ::pd_menus::clear_recentfiles_menu {} {
-    set ::recentfiles_list {}
-    ::pd_menus::update_recentfiles_menu
-}
-
-proc ::pd_menus::update_openrecent_menu_aqua {mymenu} {
-    if {! [winfo exists $mymenu]} {menu $mymenu}
-    $mymenu delete 0 end
-    foreach filename $::recentfiles_list {
-        puts "creating menu item for $filename"
-        $mymenu add command -label [file tail $filename] \
-            -command "open_file $filename"
-    }
-    $mymenu add  separator
-    $mymenu add command -label [_ "Clear Menu"] \
-        -command "::pd_menus::clear_recentfiles_menu"
-}
-
-# this expects to be run on the File menu, and to insert above the last separator
-proc ::pd_menus::update_recentfiles_on_menu {mymenu} {
-    set lastitem [$mymenu index end]
-    set i 1
-    while {[$mymenu type [expr $lastitem-$i]] ne "separator"} {incr i}
-    set bottom_separator [expr $lastitem-$i]
-    incr i
-    while {[$mymenu type [expr $lastitem-$i]] ne "separator"} {incr i}
-    set top_separator [expr $lastitem-$i]
-    if {$top_separator < [expr $bottom_separator-1]} {
-        $mymenu delete [expr $top_separator+1] [expr $bottom_separator-1]
-    }
-    set i 0
-    foreach filename $::recentfiles_list {
-        $mymenu insert [expr $top_separator+$i+1] command \
-            -label [file tail $filename] -command "open_file $filename"
-        incr i
-    }
-}
-
-# ------------------------------------------------------------------------------
-# menu building functions for Mac OS X/aqua
-
-# for Mac OS X only
-proc ::pd_menus::create_apple_menu {mymenu} {
-    # TODO this should open a Pd patch called about.pd
-    menu $mymenu.apple
-    $mymenu.apple add command -label [_ "About Pd"] \
-        -command {menu_doc_open doc/1.manual 1.introduction.txt} 
-    $mymenu add cascade -label "Apple" -menu $mymenu.apple
-    $mymenu.apple add  separator
-    # starting in 8.4.14, this is created automatically
-    set patchlevel [split [info patchlevel] .]
-    if {[lindex $patchlevel 1] < 5 && [lindex $patchlevel 2] < 14} {
-        $mymenu.apple add command -label [_ "Preferences..."] \
-            -command {menu_preferences_dialog" -accelerator "Cmd+,}
-    }
-}
-
-proc ::pd_menus::build_file_menu_aqua {mymenu} {
-    variable accelerator
-    $mymenu add command -label [_ "New"]       -accelerator "$accelerator+N"
-    $mymenu add command -label [_ "Open"]      -accelerator "$accelerator+O"
-    ::pd_menus::update_openrecent_menu_aqua .openrecent
-    $mymenu add cascade -label [_ "Open Recent"] -menu .openrecent
-    $mymenu add  separator
-    $mymenu add command -label [_ "Close"]     -accelerator "$accelerator+W"
-    $mymenu add command -label [_ "Save"]      -accelerator "$accelerator+S"
-    $mymenu add command -label [_ "Save As..."] -accelerator "$accelerator+Shift+S"
-    #$mymenu add command -label [_ "Save All"]
-    #$mymenu add command -label [_ "Revert to Saved"]
-    $mymenu add  separator
-    $mymenu add command -label [_ "Message"]
-    $mymenu add  separator
-    $mymenu add command -label [_ "Print..."]   -accelerator "$accelerator+P"
-}
-
-# the "Edit", "Put", and "Find" menus do not have cross-platform differences
-
-proc ::pd_menus::build_media_menu_aqua {mymenu} {
-}
-
-proc ::pd_menus::build_window_menu_aqua {mymenu} {
-}
-
-# the "Help" does not have cross-platform differences
- 
-# ------------------------------------------------------------------------------
-# menu building functions for UNIX/X11
-
-proc ::pd_menus::build_file_menu_x11 {mymenu} {
-    variable accelerator
-    $mymenu add command -label [_ "New"]        -accelerator "$accelerator+N"
-    $mymenu add command -label [_ "Open"]       -accelerator "$accelerator+O"
-    $mymenu add  separator
-    $mymenu add command -label [_ "Save"]       -accelerator "$accelerator+S"
-    $mymenu add command -label [_ "Save As..."] -accelerator "Shift+$accelerator+S"
-    #    $mymenu add command -label "Revert"
-    $mymenu add  separator
-    $mymenu add command -label [_ "Message"]    -accelerator "$accelerator+M"
-    $mymenu add command -label [_ "Print..."]   -accelerator "$accelerator+P"
-    $mymenu add  separator
-    # the recent files get inserted in here by update_recentfiles_on_menu
-    $mymenu add  separator
-    $mymenu add command -label [_ "Close"]      -accelerator "$accelerator+W"
-    $mymenu add command -label [_ "Quit"]       -accelerator "$accelerator+Q" \
-        -command {pdsend "pd verifyquit"}
-}
-
-# the "Edit", "Put", and "Find" menus do not have cross-platform differences
-
-proc ::pd_menus::build_media_menu_x11 {mymenu} {
-}
-
-proc ::pd_menus::build_window_menu_x11 {mymenu} {
-}
-
-# the "Help" does not have cross-platform differences
-
-# ------------------------------------------------------------------------------
-# menu building functions for Windows/Win32
-
-# for Windows only
-proc ::pd_menus::create_system_menu {mymenu} {
-    $mymenu add cascade -menu [menu $mymenu.system]
-    # TODO add Close, Minimize, etc and whatever else is on the little menu
-    # that is on the top left corner of the window frame
-}
-
-proc ::pd_menus::build_file_menu_win32 {mymenu} {
-    variable accelerator
-    $mymenu add command -label [_ "New"]      -accelerator "$accelerator+N"
-    $mymenu add command -label [_ "Open"]     -accelerator "$accelerator+O"
-    $mymenu add  separator
-    $mymenu add command -label [_ "Save"]      -accelerator "$accelerator+S"
-    $mymenu add command -label [_ "Save As..."] -accelerator "Shift+$accelerator+S"
-    #    $mymenu add command -label "Revert"
-    $mymenu add  separator
-    $mymenu add command -label [_ "Message"]  -accelerator "$accelerator+M"
-    $mymenu add command -label [_ "Print..."] -accelerator "$accelerator+P"
-    $mymenu add  separator
-    # the recent files get inserted in here by update_recentfiles_on_menu
-    $mymenu add  separator
-    $mymenu add command -label [_ "Close"]    -accelerator "$accelerator+W"
-    $mymenu add command -label [_ "Quit"]     -accelerator "$accelerator+Q"\
-        -command {pdsend "pd verifyquit"}
-}
-
-# the "Edit", "Put", and "Find" menus do not have cross-platform differences
-
-proc ::pd_menus::build_media_menu_win32 {mymenu} {
-}
-
-proc ::pd_menus::build_window_menu_win32 {mymenu} {
-}
-
-# the "Help" does not have cross-platform differences
-
diff --git a/pd/src/pd_menus_SHORT.tcl b/pd/src/pd_menus_SHORT.tcl
new file mode 100644
index 000000000..1f00434e9
--- /dev/null
+++ b/pd/src/pd_menus_SHORT.tcl
@@ -0,0 +1,90 @@
+# Copyright (c) 1997-2009 Miller Puckette.
+#(c) 2008 WordTech Communications LLC. License: standard Tcl license, http://www.tcl.tk/software/tcltk/license.html
+
+package provide pd_menus 0.1
+
+#package require pd_menucommands
+
+# TODO figure out Undo/Redo/Cut/Copy/Paste state changes for menus
+
+# since there is one menubar that is used for all windows, the menu -commands
+# use {} quotes so that $::focused_window is interpreted when the menu item
+# is called, not when the command is mapped to the menu item.  This is the
+# opposite of the 'bind' commands in pd_bindings.tcl
+    
+namespace eval ::pd_menus:: {
+    variable accelerator
+    #variable menubar ".mbar"
+
+    namespace export create_menubar
+    namespace export configure_for_pdwindow
+    namespace export configure_for_canvas
+    namespace export configure_for_dialog
+
+    # turn off tearoff menus globally
+    option add *tearOff 0
+} 
+
+# ------------------------------------------------------------------------------
+# update the menu entries for opening recent files (write arg should always be true except the first time when pd is opened)
+proc ::pd_menus::update_recentfiles_menu {menu {write true}} {
+    #variable menubar
+    switch -- $::windowingsystem {
+        "aqua"  {::pd_menus::update_openrecent_menu_aqua $menu $write}
+        "win32" {::pd_menus::update_recentfiles_on_menu $menu $write}
+        "x11"   {::pd_menus::update_recentfiles_on_menu $menu $write}
+    }
+}
+
+proc ::pd_menus::clear_recentfiles_menu {} {
+    set ::recentfiles_list {}
+    #::pd_menus::update_recentfiles_menu
+    # empty recentfiles in preferences (write empty array)
+    ::pd_guiprefs::write_recentfiles
+}
+
+proc ::pd_menus::update_openrecent_menu_aqua {mymenu {write}} {
+    if {! [winfo exists $mymenu]} {menu $mymenu}
+    $mymenu delete 0 end
+
+    # now the list is last first so we just add
+    foreach filename $::recentfiles_list {
+        $mymenu add command -label [file tail $filename] \
+            -command "open_file {$filename}"
+    }
+    # clear button
+    $mymenu add  separator
+    $mymenu add command -label [_ "Clear Menu"] \
+        -command "::pd_menus::clear_recentfiles_menu"
+    # write to config file
+    if {$write == true} { ::pd_guiprefs::write_recentfiles }
+}
+
+# this expects to be run on the File menu, and to insert above the last separator
+proc ::pd_menus::update_recentfiles_on_menu {mymenu {write}} {
+    set lastitem [$mymenu index end]
+    set i 0
+    while {[$mymenu entrycget [expr $lastitem-$i] -label] ne ""} {incr i}
+    set bottom_separator [expr $lastitem-$i]
+    incr i
+
+    while {[$mymenu entrycget [expr $lastitem-$i] -label] ne ""} {incr i}
+    set top_separator [expr $lastitem-$i]
+    if {$top_separator < [expr $bottom_separator-1]} {
+        $mymenu delete [expr $top_separator+1] [expr $bottom_separator-1]
+    }
+    # insert the list from the end because we insert each element on the top
+    set i [llength $::recentfiles_list]
+    while {[incr i -1] > 0} {
+
+        set filename [lindex $::recentfiles_list $i]
+        $mymenu insert [expr $top_separator+1] command \
+            -label [file tail $filename] -command "open_file {$filename}"
+    }
+    set filename [lindex $::recentfiles_list 0]
+    $mymenu insert [expr $top_separator+1] command \
+        -label [file tail $filename] -command "open_file {$filename}"
+
+    # write to config file
+    if {$write == true} { ::pd_guiprefs::write_recentfiles }
+}
\ No newline at end of file
diff --git a/pd/src/pkgIndex.tcl b/pd/src/pkgIndex.tcl
index 41c87108d..29d492f0c 100644
--- a/pd/src/pkgIndex.tcl
+++ b/pd/src/pkgIndex.tcl
@@ -2,6 +2,8 @@
 #
 
 package ifneeded helpbrowser 0.1 [list source [file join $dir helpbrowser.tcl]]
+package ifneeded pd_guiprefs 0.1 [list source [file join $dir pd_guiprefs.tcl]]
+package ifneeded pd_menus 0.1 [list source [file join $dir pd_menus_SHORT.tcl]]
 
 namespace eval ::tkpath {
     proc load_package {dir} {
diff --git a/pd/tcl/AppMain.tcl b/pd/tcl/AppMain.tcl
index b170c6f5c..7c68a9d04 100644
--- a/pd/tcl/AppMain.tcl
+++ b/pd/tcl/AppMain.tcl
@@ -3,17 +3,8 @@
 # other configuration, like when 'pd' launches Wish.app or when 'pd' is using
 # an X11 wish on Mac OS X.  GNU/Linux and Windows will never use this file.
 
-
-puts --------------------------AppMain.tcl-----------------------------------
-catch {console show}
-
 package require apple_events
 
-puts "AppMain.tcl"
-puts "argv0: $argv0"
-puts "executable: [info nameofexecutable]"
-puts "argc: $argc argv: $argv"
-
 # TODO is there anything useful to do with the psn (Process Serial Number)?
 if {[string first "-psn" [lindex $argv 0]] == 0} { 
     set argv [lrange $argv 1 end]
diff --git a/pd/tcl/Makefile.am b/pd/tcl/Makefile.am
new file mode 100644
index 000000000..3f7809e09
--- /dev/null
+++ b/pd/tcl/Makefile.am
@@ -0,0 +1,16 @@
+AUTOMAKE_OPTIONS = foreign
+
+SUFFIXES = .tcl
+
+# we want these in the dist tarball
+#EXTRA_DIST = CHANGELOG.txt notes.txt makefile.mingw
+
+
+bin_SCRIPTS = pd-gui.tcl
+
+libpdtcldir = $(pkglibdir)/tcl
+dist_libpdtcl_SCRIPTS = pd-gui.tcl
+dist_libpdtcl_DATA = apple_events.tcl dialog_canvas.tcl dialog_gatom.tcl dialog_path.tcl pd_bindings.tcl pd_menus.tcl pdwindow.tcl scrollboxwindow.tcl AppMain.tcl dialog_data.tcl dialog_iemgui.tcl dialog_startup.tcl pd_connect.tcl pkgIndex.tcl wheredoesthisgo.tcl dialog_array.tcl dialog_find.tcl dialog_message.tcl helpbrowser.tcl pdtk_canvas.tcl pkg_mkIndex.tcl dialog_audio.tcl dialog_font.tcl dialog_midi.tcl opt_parser.tcl pd_menucommands.tcl pdtk_text.tcl pdtk_textwindow.tcl scrollbox.tcl pd_guiprefs.tcl pd.ico
+
+etags: TAGS
+	etags --append --language=none --regex="/proc[ \t]+\([^ \t]+\)/\1/" *.tcl
diff --git a/pd/tcl/apple_events.tcl b/pd/tcl/apple_events.tcl
index cfc92982b..0311add73 100644
--- a/pd/tcl/apple_events.tcl
+++ b/pd/tcl/apple_events.tcl
@@ -1,53 +1,65 @@
 
 package provide apple_events 0.1
 
+package require pdwindow
 package require wheredoesthisgo
 
 # from http://wiki.tcl.tk/12987
 
 set ::tk::mac::CGAntialiasLimit 0 ;# min line thickness to anti-alias (default: 3)
-set ::tk::mac::antialiasedtext  1 ;# enable/disable anti-aliased text
+set ::tk::mac::antialiasedtext  1 ;# enable anti-aliased text
 
 # kAEOpenDocuments
 proc ::tk::mac::OpenDocument {args} {
-    foreach filename $args { 
-        puts "open_file $filename"
-        open_file $filename
+    foreach filename $args {
+        if {$::done_init} {
+            open_file $filename
+        } else {
+            lappend ::filestoopen_list $filename
+        }
     }
     set ::pd_menucommands::menu_open_dir [file dirname $filename]
 }
 
 # kEventAppHidden
-proc ::tk::mac::OnHide {} {
-    # TODO
+proc ::tk::mac::OnHide {args} {
+    ::pdwindow::verbose 1 "::tk::mac::OnHide $args +++++++++++++++++++++"
 }
 
 # kEventAppShown
-proc ::tk::mac::OnShow {} {
-    # TODO
+proc ::tk::mac::OnShow {args} {
+    ::pdwindow::verbose 1 "::tk::mac::OnShow $args +++++++++++++++++++++"
+}
+
+# open About Pd... in Tk/Cocoa
+proc tkAboutDialog {} {
+    menu_aboutpd
 }
 
 # kAEShowPreferences
-proc ::tk::mac::ShowPreferences {} {
-     menu_preferences_dialog
+proc ::tk::mac::ShowPreferences {args} {
+    ::pdwindow::verbose 1 "::tk::mac::ShowPreferences $args ++++++++++++"
+    pdsend "pd start-path-dialog"
 }
 
 # kAEQuitApplication
-#proc ::tk::mac::Quit {} {
-#    # TODO sort this out... how to quit pd-gui after sending the message
-#    puts stderr "Custom exit proc"
-#    pdsend "pd verifyquit"
+proc ::tk::mac::Quit {args} {
+    pdsend "pd verifyquit"
+}
+
+# on Tk/Cocoa, override the Apple Help menu
+#proc tk::mac::ShowHelp {args} {
 #}
 
 # these I gleaned by reading the source (tkMacOSXHLEvents.c)
 proc ::tk::mac::PrintDocument {args} {
-    # TODO what's $mytoplevel here?.  I am guessing args would be the same as
-    # ::tk::mac::OpenDocument
-    #menu_print $mytoplevel
+    menu_print $::focused_window
 }
 
-proc ::tk::mac::OpenApplication {} {
+proc ::tk::mac::OpenApplication {args} {
+    ::pdwindow::verbose 1 "::tk::mac::OpenApplication $args ++++++++++++"
 }
 
-proc ::tk::mac::ReopenApplication {} {
+proc ::tk::mac::ReopenApplication {args} {
+    ::pdwindow::verbose 1 "::tk::mac::ReopenApplication $args ++++++++++"
 }
diff --git a/pd/tcl/dialog_array.tcl b/pd/tcl/dialog_array.tcl
index 87b2de8ce..0f2696d2d 100644
--- a/pd/tcl/dialog_array.tcl
+++ b/pd/tcl/dialog_array.tcl
@@ -239,7 +239,6 @@ proc ::dialog_array::ok {mytoplevel} {
 }
 
 proc ::dialog_array::pdtk_array_dialog {mytoplevel name size flags newone} {
-puts "::dialog_array::pdtk_array_dialog {$mytoplevel $name $size $flags $newone}"
     if {[winfo exists $mytoplevel]} {
         wm deiconify $mytoplevel
         raise $mytoplevel
@@ -262,7 +261,11 @@ puts "::dialog_array::pdtk_array_dialog {$mytoplevel $name $size $flags $newone}
 proc ::dialog_array::create_dialog {mytoplevel newone} {
     toplevel $mytoplevel -class DialogWindow
     wm title $mytoplevel [_ "Array Properties"]
-    if {$::windowingsystem eq "aqua"} {$mytoplevel configure -menu .menubar}
+    wm group $mytoplevel .
+    wm resizable $mytoplevel 0 0
+    wm transient $mytoplevel $::focused_window
+    $mytoplevel configure -menu $::dialog_menubar
+    $mytoplevel configure -padx 0 -pady 0
     ::pd_bindings::dialog_bindings $mytoplevel "array"
 
     frame $mytoplevel.name
@@ -315,14 +318,16 @@ proc ::dialog_array::create_dialog {mytoplevel newone} {
     }
     # end jsarlo
     frame $mytoplevel.buttonframe
-    pack $mytoplevel.buttonframe -side bottom -fill x -pady 2m
+    pack $mytoplevel.buttonframe -side bottom -expand 1 -fill x -pady 2m
     button $mytoplevel.buttonframe.cancel -text [_ "Cancel"] \
         -command "::dialog_array::cancel $mytoplevel"
-    if {$newone == 0} {button $mytoplevel.buttonframe.apply -text [_ "Apply"] \
-                           -command "::dialog_array::apply $mytoplevel"}
+    pack $mytoplevel.buttonframe.cancel -side left -expand 1 -fill x -padx 10
+    if {$newone == 0 && $::windowingsystem ne "aqua"} {
+        button $mytoplevel.buttonframe.apply -text [_ "Apply"] \
+            -command "::dialog_array::apply $mytoplevel"
+        pack $mytoplevel.buttonframe.apply -side left -expand 1 -fill x -padx 10
+    }
     button $mytoplevel.buttonframe.ok -text [_ "OK"]\
         -command "::dialog_array::ok $mytoplevel"
-    pack $mytoplevel.buttonframe.cancel -side left -expand 1
-    if {$newone == 0} {pack $mytoplevel.buttonframe.apply -side left -expand 1}
-    pack $mytoplevel.buttonframe.ok -side left -expand 1
+    pack $mytoplevel.buttonframe.ok -side left -expand 1 -fill x -padx 10
 }
diff --git a/pd/tcl/dialog_audio.tcl b/pd/tcl/dialog_audio.tcl
index 1025f66e0..56f18f455 100644
--- a/pd/tcl/dialog_audio.tcl
+++ b/pd/tcl/dialog_audio.tcl
@@ -4,19 +4,20 @@ namespace eval ::dialog_audio:: {
     namespace export pdtk_audio_dialog
 }
 
-# TODO this panel really needs some reworking, it works but the code is
-# very unreadable
+# TODO this panel really needs some reworking, it works but the code is very
+# unreadable.  The panel could look a lot better too, like using menubuttons
+# instead of regular buttons with tk_popup for pulldown menus.
 
 ####################### audio dialog ##################3
 
-proc ::dialog_audio::apply {id} {
+proc ::dialog_audio::apply {mytoplevel} {
     global audio_indev1 audio_indev2 audio_indev3 audio_indev4 
     global audio_inchan1 audio_inchan2 audio_inchan3 audio_inchan4
     global audio_inenable1 audio_inenable2 audio_inenable3 audio_inenable4
     global audio_outdev1 audio_outdev2 audio_outdev3 audio_outdev4 
     global audio_outchan1 audio_outchan2 audio_outchan3 audio_outchan4
     global audio_outenable1 audio_outenable2 audio_outenable3 audio_outenable4
-    global audio_sr audio_advance audio_callback
+    global audio_sr audio_advance audio_callback audio_blocksize
 
     pdsend "pd audio-dialog \
         $audio_indev1 \
@@ -37,16 +38,17 @@ proc ::dialog_audio::apply {id} {
         [expr $audio_outchan4 * ( $audio_outenable4 ? 1 : -1 ) ]\
         $audio_sr \
         $audio_advance \
-        $audio_callback"
+        $audio_callback \
+        $audio_blocksize"
 }
 
-proc ::dialog_audio::cancel {id} {
-    pdsend "$id cancel"
+proc ::dialog_audio::cancel {mytoplevel} {
+    pdsend "$mytoplevel cancel"
 }
 
-proc ::dialog_audio::ok {id} {
-    ::dialog_audio::apply $id
-    ::dialog_audio::cancel $id
+proc ::dialog_audio::ok {mytoplevel} {
+    ::dialog_audio::apply $mytoplevel
+    ::dialog_audio::cancel $mytoplevel
 }
 
 # callback from popup menu
@@ -78,18 +80,19 @@ proc audio_popup {name buttonname varname devlist} {
 # opening several devices; if not, we get an extra button to turn longform
 # on and restart the dialog.
 
-proc ::dialog_audio::pdtk_audio_dialog {id indev1 indev2 indev3 indev4 \
+proc ::dialog_audio::pdtk_audio_dialog {mytoplevel \
+        indev1 indev2 indev3 indev4 \
         inchan1 inchan2 inchan3 inchan4 \
         outdev1 outdev2 outdev3 outdev4 \
         outchan1 outchan2 outchan3 outchan4 sr advance multi callback \
-        longform} {
+        longform blocksize} {
     global audio_indev1 audio_indev2 audio_indev3 audio_indev4 
     global audio_inchan1 audio_inchan2 audio_inchan3 audio_inchan4
     global audio_inenable1 audio_inenable2 audio_inenable3 audio_inenable4
     global audio_outdev1 audio_outdev2 audio_outdev3 audio_outdev4
     global audio_outchan1 audio_outchan2 audio_outchan3 audio_outchan4
     global audio_outenable1 audio_outenable2 audio_outenable3 audio_outenable4
-    global audio_sr audio_advance audio_callback
+    global audio_sr audio_advance audio_callback audio_blocksize
     global audio_indevlist audio_outdevlist
     global pd_indev pd_outdev
     global audio_longform
@@ -125,174 +128,196 @@ proc ::dialog_audio::pdtk_audio_dialog {id indev1 indev2 indev3 indev4 \
     set audio_sr $sr
     set audio_advance $advance
     set audio_callback $callback
-
-    toplevel $id
-    wm title $id [_ "Audio Settings"]
-    if {$::windowingsystem eq "aqua"} {$id configure -menu .menubar}
-    ::pd_bindings::dialog_bindings $id "audio"
-
-    frame $id.buttonframe
-    pack $id.buttonframe -side bottom -fill x -pady 2m
-    button $id.buttonframe.cancel -text [_ "Cancel"]\
-        -command "::dialog_audio::cancel $id"
-    button $id.buttonframe.apply -text [_ "Apply"]\
-        -command "::dialog_audio::apply $id"
-    button $id.buttonframe.ok -text [_ "OK"]\
-        -command "::dialog_audio::ok $id"
-    button $id.buttonframe.save -text [_ "Save all settings"]\
-        -command "::dialog_audio::apply $id \; pdsend \"pd save-preferences\""
-    pack $id.buttonframe.cancel $id.buttonframe.apply $id.buttonframe.ok \
-        $id.buttonframe.save -side left -expand 1
+    set audio_blocksize $blocksize
+
+    toplevel $mytoplevel -class DialogWindow
+    wm title $mytoplevel [_ "Audio Settings"]
+    wm group $mytoplevel .
+    wm resizable $mytoplevel 0 0
+    wm transient $mytoplevel
+    $mytoplevel configure -menu $::dialog_menubar
+    $mytoplevel configure -padx 10 -pady 5
+    ::pd_bindings::dialog_bindings $mytoplevel "audio"
+    # not all Tcl/Tk versions or platforms support -topmost, so catch the error
+    catch {wm attributes $mytoplevel -topmost 1}
+
+    frame $mytoplevel.buttonframe
+    pack $mytoplevel.buttonframe -side bottom -fill x -pady 2m
+    button $mytoplevel.buttonframe.cancel -text [_ "Cancel"]\
+        -command "::dialog_audio::cancel $mytoplevel"
+    pack $mytoplevel.buttonframe.cancel -side left -expand 1 -fill x -padx 15
+    button $mytoplevel.buttonframe.apply -text [_ "Apply"]\
+        -command "::dialog_audio::apply $mytoplevel"
+    pack $mytoplevel.buttonframe.apply -side left -expand 1 -fill x -padx 15
+    button $mytoplevel.buttonframe.ok -text [_ "OK"] \
+        -command "::dialog_audio::ok $mytoplevel"
+    pack $mytoplevel.buttonframe.ok -side left -expand 1 -fill x -padx 15
+
+    button $mytoplevel.saveall -text [_ "Save All Settings"]\
+        -command "::dialog_audio::apply $mytoplevel; pdsend {pd save-preferences}"
+    pack $mytoplevel.saveall -side bottom -expand 1 -pady 5
     
         # sample rate and advance
-    frame $id.srf
-    pack $id.srf -side top
+    frame $mytoplevel.srf
+    pack $mytoplevel.srf -side top
     
-    label $id.srf.l1 -text [_ "Sample rate:"]
-    entry $id.srf.x1 -textvariable audio_sr -width 7
-    label $id.srf.l2 -text [_ "Delay (msec):"]
-    entry $id.srf.x2 -textvariable audio_advance -width 4
-    pack $id.srf.l1 $id.srf.x1 $id.srf.l2 $id.srf.x2 -side left
+    label $mytoplevel.srf.l1 -text [_ "Sample rate:"]
+    entry $mytoplevel.srf.x1 -textvariable audio_sr -width 7
+    label $mytoplevel.srf.l2 -text [_ "Delay (msec):"]
+    entry $mytoplevel.srf.x2 -textvariable audio_advance -width 4
+
+    label $mytoplevel.srf.l3 -text [_ "Block size:"]
+    tk_optionMenu $mytoplevel.srf.x3 audio_blocksize 64 128 256 512 1024 2048
+
+    pack $mytoplevel.srf.l1 $mytoplevel.srf.x1 $mytoplevel.srf.l2 \
+        $mytoplevel.srf.x2 $mytoplevel.srf.l3 $mytoplevel.srf.x3 -side left
     if {$audio_callback >= 0} {
-        checkbutton $id.srf.x3 -variable audio_callback \
+        checkbutton $mytoplevel.srf.x4 -variable audio_callback \
             -text [_ "Use callbacks"] -anchor e
-        pack $id.srf.x3 -side left
+        pack $mytoplevel.srf.x4 -side left
     }
         # input device 1
-    frame $id.in1f
-    pack $id.in1f -side top
+    frame $mytoplevel.in1f
+    pack $mytoplevel.in1f -side top
 
-    checkbutton $id.in1f.x0 -variable audio_inenable1 \
+    checkbutton $mytoplevel.in1f.x0 -variable audio_inenable1 \
         -text [_ "Input device 1:"] -anchor e
-    button $id.in1f.x1 -text [lindex $audio_indevlist $audio_indev1] \
-        -command [list audio_popup $id $id.in1f.x1 audio_indev1 $audio_indevlist]
-    label $id.in1f.l2 -text [_ "Channels:"]
-    entry $id.in1f.x2 -textvariable audio_inchan1 -width 3
-    pack $id.in1f.x0 $id.in1f.x1 $id.in1f.l2 $id.in1f.x2 -side left -fill x
+    button $mytoplevel.in1f.x1 -text [lindex $audio_indevlist $audio_indev1] \
+        -command [list audio_popup $mytoplevel $mytoplevel.in1f.x1 audio_indev1 $audio_indevlist]
+    label $mytoplevel.in1f.l2 -text [_ "Channels:"]
+    entry $mytoplevel.in1f.x2 -textvariable audio_inchan1 -width 3
+    pack $mytoplevel.in1f.x0 $mytoplevel.in1f.x1 $mytoplevel.in1f.l2 \
+        $mytoplevel.in1f.x2 -side left -fill x
 
         # input device 2
     if {$longform && $multi > 1 && [llength $audio_indevlist] > 1} {
-        frame $id.in2f
-        pack $id.in2f -side top
+        frame $mytoplevel.in2f
+        pack $mytoplevel.in2f -side top
 
-        checkbutton $id.in2f.x0 -variable audio_inenable2 \
+        checkbutton $mytoplevel.in2f.x0 -variable audio_inenable2 \
             -text [_ "Input device 2:"] -anchor e
-        button $id.in2f.x1 -text [lindex $audio_indevlist $audio_indev2] \
-            -command [list audio_popup $id $id.in2f.x1 audio_indev2 \
+        button $mytoplevel.in2f.x1 -text [lindex $audio_indevlist $audio_indev2] \
+            -command [list audio_popup $mytoplevel $mytoplevel.in2f.x1 audio_indev2 \
                 $audio_indevlist]
-        label $id.in2f.l2 -text [_ "Channels:"]
-        entry $id.in2f.x2 -textvariable audio_inchan2 -width 3
-        pack $id.in2f.x0 $id.in2f.x1 $id.in2f.l2 $id.in2f.x2 -side left -fill x
+        label $mytoplevel.in2f.l2 -text [_ "Channels:"]
+        entry $mytoplevel.in2f.x2 -textvariable audio_inchan2 -width 3
+        pack $mytoplevel.in2f.x0 $mytoplevel.in2f.x1 $mytoplevel.in2f.l2 \
+            $mytoplevel.in2f.x2 -side left -fill x
     }
 
         # input device 3
     if {$longform && $multi > 1 && [llength $audio_indevlist] > 2} {
-        frame $id.in3f
-        pack $id.in3f -side top
+        frame $mytoplevel.in3f
+        pack $mytoplevel.in3f -side top
 
-        checkbutton $id.in3f.x0 -variable audio_inenable3 \
+        checkbutton $mytoplevel.in3f.x0 -variable audio_inenable3 \
             -text [_ "Input device 3:"] -anchor e
-        button $id.in3f.x1 -text [lindex $audio_indevlist $audio_indev3] \
-            -command [list audio_popup $id $id.in3f.x1 audio_indev3 \
+        button $mytoplevel.in3f.x1 -text [lindex $audio_indevlist $audio_indev3] \
+            -command [list audio_popup $mytoplevel $mytoplevel.in3f.x1 audio_indev3 \
                 $audio_indevlist]
-        label $id.in3f.l2 -text [_ "Channels:"]
-        entry $id.in3f.x2 -textvariable audio_inchan3 -width 3
-        pack $id.in3f.x0 $id.in3f.x1 $id.in3f.l2 $id.in3f.x2 -side left
+        label $mytoplevel.in3f.l2 -text [_ "Channels:"]
+        entry $mytoplevel.in3f.x2 -textvariable audio_inchan3 -width 3
+        pack $mytoplevel.in3f.x0 $mytoplevel.in3f.x1 $mytoplevel.in3f.l2 $mytoplevel.in3f.x2 -side left
     }
 
         # input device 4
     if {$longform && $multi > 1 && [llength $audio_indevlist] > 3} {
-        frame $id.in4f
-        pack $id.in4f -side top
+        frame $mytoplevel.in4f
+        pack $mytoplevel.in4f -side top
 
-        checkbutton $id.in4f.x0 -variable audio_inenable4 \
+        checkbutton $mytoplevel.in4f.x0 -variable audio_inenable4 \
             -text [_ "Input device 4:"] -anchor e
-        button $id.in4f.x1 -text [lindex $audio_indevlist $audio_indev4] \
-            -command [list audio_popup $id $id.in4f.x1 audio_indev4 \
+        button $mytoplevel.in4f.x1 -text [lindex $audio_indevlist $audio_indev4] \
+            -command [list audio_popup $mytoplevel $mytoplevel.in4f.x1 audio_indev4 \
                 $audio_indevlist]
-        label $id.in4f.l2 -text [_ "Channels:"]
-        entry $id.in4f.x2 -textvariable audio_inchan4 -width 3
-        pack $id.in4f.x0 $id.in4f.x1 $id.in4f.l2 $id.in4f.x2 -side left
+        label $mytoplevel.in4f.l2 -text [_ "Channels:"]
+        entry $mytoplevel.in4f.x2 -textvariable audio_inchan4 -width 3
+        pack $mytoplevel.in4f.x0 $mytoplevel.in4f.x1 $mytoplevel.in4f.l2 \
+            $mytoplevel.in4f.x2 -side left
     }
 
         # output device 1
-    frame $id.out1f
-    pack $id.out1f -side top
+    frame $mytoplevel.out1f
+    pack $mytoplevel.out1f -side top
 
-    checkbutton $id.out1f.x0 -variable audio_outenable1 \
+    checkbutton $mytoplevel.out1f.x0 -variable audio_outenable1 \
         -text [_ "Output device 1:"] -anchor e
     if {$multi == 0} {
-        label $id.out1f.l1 \
+        label $mytoplevel.out1f.l1 \
             -text [_ "(same as input device) ..............      "]
     } else {
-        button $id.out1f.x1 -text [lindex $audio_outdevlist $audio_outdev1] \
-            -command  [list audio_popup $id $id.out1f.x1 audio_outdev1 \
+        button $mytoplevel.out1f.x1 -text [lindex $audio_outdevlist $audio_outdev1] \
+            -command  [list audio_popup $mytoplevel $mytoplevel.out1f.x1 audio_outdev1 \
                 $audio_outdevlist]
     }
-    label $id.out1f.l2 -text [_ "Channels:"]
-    entry $id.out1f.x2 -textvariable audio_outchan1 -width 3
+    label $mytoplevel.out1f.l2 -text [_ "Channels:"]
+    entry $mytoplevel.out1f.x2 -textvariable audio_outchan1 -width 3
     if {$multi == 0} {
-        pack $id.out1f.x0 $id.out1f.l1 $id.out1f.x2 -side left -fill x
+        pack $mytoplevel.out1f.x0 $mytoplevel.out1f.l1 $mytoplevel.out1f.x2 -side left -fill x
     } else {
-        pack $id.out1f.x0 $id.out1f.x1 $id.out1f.l2 $id.out1f.x2 -side left -fill x
+        pack $mytoplevel.out1f.x0 $mytoplevel.out1f.x1 $mytoplevel.out1f.l2\
+            $mytoplevel.out1f.x2 -side left -fill x
     }
 
         # output device 2
     if {$longform && $multi > 1 && [llength $audio_outdevlist] > 1} {
-        frame $id.out2f
-        pack $id.out2f -side top
+        frame $mytoplevel.out2f
+        pack $mytoplevel.out2f -side top
 
-        checkbutton $id.out2f.x0 -variable audio_outenable2 \
+        checkbutton $mytoplevel.out2f.x0 -variable audio_outenable2 \
             -text [_ "Output device 2:"] -anchor e
-        button $id.out2f.x1 -text [lindex $audio_outdevlist $audio_outdev2] \
+        button $mytoplevel.out2f.x1 -text [lindex $audio_outdevlist $audio_outdev2] \
             -command \
-            [list audio_popup $id $id.out2f.x1 audio_outdev2 $audio_outdevlist]
-        label $id.out2f.l2 -text [_ "Channels:"]
-        entry $id.out2f.x2 -textvariable audio_outchan2 -width 3
-        pack $id.out2f.x0 $id.out2f.x1 $id.out2f.l2 $id.out2f.x2 -side left
+            [list audio_popup $mytoplevel $mytoplevel.out2f.x1 audio_outdev2 $audio_outdevlist]
+        label $mytoplevel.out2f.l2 -text [_ "Channels:"]
+        entry $mytoplevel.out2f.x2 -textvariable audio_outchan2 -width 3
+        pack $mytoplevel.out2f.x0 $mytoplevel.out2f.x1 $mytoplevel.out2f.l2\
+            $mytoplevel.out2f.x2 -side left
     }
 
         # output device 3
     if {$longform && $multi > 1 && [llength $audio_outdevlist] > 2} {
-        frame $id.out3f
-        pack $id.out3f -side top
+        frame $mytoplevel.out3f
+        pack $mytoplevel.out3f -side top
 
-        checkbutton $id.out3f.x0 -variable audio_outenable3 \
+        checkbutton $mytoplevel.out3f.x0 -variable audio_outenable3 \
             -text [_ "Output device 3:"] -anchor e
-        button $id.out3f.x1 -text [lindex $audio_outdevlist $audio_outdev3] \
+        button $mytoplevel.out3f.x1 -text [lindex $audio_outdevlist $audio_outdev3] \
             -command \
-            [list audio_popup $id $id.out3f.x1 audio_outdev3 $audio_outdevlist]
-        label $id.out3f.l2 -text [_ "Channels:"]
-        entry $id.out3f.x2 -textvariable audio_outchan3 -width 3
-        pack $id.out3f.x0 $id.out3f.x1 $id.out3f.l2 $id.out3f.x2 -side left
+            [list audio_popup $mytoplevel $mytoplevel.out3f.x1 audio_outdev3 $audio_outdevlist]
+        label $mytoplevel.out3f.l2 -text [_ "Channels:"]
+        entry $mytoplevel.out3f.x2 -textvariable audio_outchan3 -width 3
+        pack $mytoplevel.out3f.x0 $mytoplevel.out3f.x1 $mytoplevel.out3f.l2 \
+            $mytoplevel.out3f.x2 -side left
     }
 
         # output device 4
     if {$longform && $multi > 1 && [llength $audio_outdevlist] > 3} {
-        frame $id.out4f
-        pack $id.out4f -side top
+        frame $mytoplevel.out4f
+        pack $mytoplevel.out4f -side top
 
-        checkbutton $id.out4f.x0 -variable audio_outenable4 \
+        checkbutton $mytoplevel.out4f.x0 -variable audio_outenable4 \
             -text [_ "Output device 4:"] -anchor e
-        button $id.out4f.x1 -text [lindex $audio_outdevlist $audio_outdev4] \
+        button $mytoplevel.out4f.x1 -text [lindex $audio_outdevlist $audio_outdev4] \
             -command \
-            [list audio_popup $id $id.out4f.x1 audio_outdev4 $audio_outdevlist]
-        label $id.out4f.l2 -text [_ "Channels:"]
-        entry $id.out4f.x2 -textvariable audio_outchan4 -width 3
-        pack $id.out4f.x0 $id.out4f.x1 $id.out4f.l2 $id.out4f.x2 -side left
+            [list audio_popup $mytoplevel $mytoplevel.out4f.x1 audio_outdev4 $audio_outdevlist]
+        label $mytoplevel.out4f.l2 -text [_ "Channels:"]
+        entry $mytoplevel.out4f.x2 -textvariable audio_outchan4 -width 3
+        pack $mytoplevel.out4f.x0 $mytoplevel.out4f.x1 $mytoplevel.out4f.l2 \
+            $mytoplevel.out4f.x2 -side left
     }
 
         # if not the "long form" but if "multi" is 2, make a button to
         # restart with longform set. 
     
     if {$longform == 0 && $multi > 1} {
-        frame $id.longbutton
-        pack $id.longbutton -side top
-        button $id.longbutton.b -text [_ "Use multiple devices"] \
+        frame $mytoplevel.longbutton
+        pack $mytoplevel.longbutton -side top
+        button $mytoplevel.longbutton.b -text [_ "Use multiple devices"] \
             -command  {pdsend "pd audio-properties 1"}
-        pack $id.longbutton.b
+        pack $mytoplevel.longbutton.b
     }
-    $id.srf.x1 select from 0
-    $id.srf.x1 select adjust end
-    focus $id.srf.x1
+    $mytoplevel.srf.x1 select from 0
+    $mytoplevel.srf.x1 select adjust end
+    focus $mytoplevel.srf.x1
 }
diff --git a/pd/tcl/dialog_canvas.tcl b/pd/tcl/dialog_canvas.tcl
index 06444807e..ea3f5d65a 100644
--- a/pd/tcl/dialog_canvas.tcl
+++ b/pd/tcl/dialog_canvas.tcl
@@ -104,7 +104,6 @@ proc ::dialog_canvas::pdtk_canvas_dialog {mytoplevel xscale yscale graphmeflags
     } else {
         create_dialog $mytoplevel
     }
-    puts "canvas_dialog $mytoplevel"
     switch -- $graphmeflags {
         0 {
             $mytoplevel.parent.graphme deselect
@@ -119,7 +118,7 @@ proc ::dialog_canvas::pdtk_canvas_dialog {mytoplevel xscale yscale graphmeflags
             $mytoplevel.parent.graphme select
             $mytoplevel.parent.hidetext select
         } default {
-            pdtk_post "Warning: unknown graphme flags received in pdtk_canvas_dialog"
+            ::pdwindow::error [_ "WARNING: unknown graphme flags received in pdtk_canvas_dialog"]
         }
     }
 
@@ -131,8 +130,8 @@ proc ::dialog_canvas::pdtk_canvas_dialog {mytoplevel xscale yscale graphmeflags
     $mytoplevel.range.y.to_entry insert 0 $yto
     $mytoplevel.range.x.size_entry insert 0 $xsize
     $mytoplevel.range.y.size_entry insert 0 $ysize
-    $mytoplevel.range.x.margin_entry insert 0 $xsize
-    $mytoplevel.range.y.margin_entry insert 0 $ysize
+    $mytoplevel.range.x.margin_entry insert 0 $xmargin
+    $mytoplevel.range.y.margin_entry insert 0 $ymargin
 
    ::dialog_canvas::checkcommand $mytoplevel
 }
@@ -140,7 +139,11 @@ proc ::dialog_canvas::pdtk_canvas_dialog {mytoplevel xscale yscale graphmeflags
 proc ::dialog_canvas::create_dialog {mytoplevel} {
     toplevel $mytoplevel -class DialogWindow
     wm title $mytoplevel [_ "Canvas Properties"]
-    if {$::windowingsystem eq "aqua"} {$mytoplevel configure -menu .menubar}
+    wm group $mytoplevel .
+    wm resizable $mytoplevel 0 0
+    wm transient $mytoplevel $::focused_window
+    $mytoplevel configure -menu $::dialog_menubar
+    $mytoplevel configure -padx 0 -pady 0
     ::pd_bindings::dialog_bindings $mytoplevel "canvas"
     
     labelframe $mytoplevel.scale -text [_ "Scale"] -borderwidth 1
@@ -201,13 +204,16 @@ proc ::dialog_canvas::create_dialog {mytoplevel} {
         -side left
 
     frame $mytoplevel.buttons
-    pack $mytoplevel.buttons -side bottom -fill x -pady 2m
+    pack $mytoplevel.buttons -side bottom -fill x -expand 1 -pady 2m
     button $mytoplevel.buttons.cancel -text [_ "Cancel"] \
         -command "::dialog_canvas::cancel $mytoplevel"
-    button $mytoplevel.buttons.apply -text [_ "Apply"] \
-        -command "::dialog_canvas::apply $mytoplevel"
+    pack $mytoplevel.buttons.cancel -side left -expand 1 -fill x -padx 10
+    if {$::windowingsystem ne "aqua"} {
+        button $mytoplevel.buttons.apply -text [_ "Apply"] \
+            -command "::dialog_canvas::apply $mytoplevel"
+        pack $mytoplevel.buttons.apply -side left -expand 1 -fill x -padx 10
+    }
     button $mytoplevel.buttons.ok -text [_ "OK"] \
         -command "::dialog_canvas::ok $mytoplevel"
-    pack $mytoplevel.buttons.cancel $mytoplevel.buttons.apply \
-        $mytoplevel.buttons.ok -side left -expand 1
+    pack $mytoplevel.buttons.ok -side left -expand 1 -fill x -padx 10
  }
diff --git a/pd/tcl/dialog_data.tcl b/pd/tcl/dialog_data.tcl
new file mode 100644
index 000000000..0bc989f53
--- /dev/null
+++ b/pd/tcl/dialog_data.tcl
@@ -0,0 +1,53 @@
+
+package provide dialog_data 0.1
+
+namespace eval ::dialog_data:: {
+    namespace export pdtk_data_dialog
+}
+
+############ pdtk_data_dialog -- run a data dialog #########
+
+proc ::dialog_data::send {mytoplevel} {
+    for {set i 1} {[$mytoplevel.text compare [concat $i.0 + 3 chars] < end]} \
+        {incr i 1} {
+            pdsend "$mytoplevel data [$mytoplevel.text get $i.0 [expr $i + 1].0]"
+        }
+    pdsend "$mytoplevel end"
+}
+
+proc ::dialog_data::cancel {mytoplevel} {
+    pdsend "$mytoplevel cancel"
+}
+
+proc ::dialog_data::ok {mytoplevel} {
+    ::dialog_data::send $mytoplevel
+    ::dialog_data::cancel $mytoplevel
+}
+
+proc ::dialog_data::pdtk_data_dialog {mytoplevel stuff} {
+    toplevel $mytoplevel -class DialogWindow
+    wm title $mytoplevel [_ "Data Properties"]
+    wm group $mytoplevel $::focused_window
+    wm transient $mytoplevel $::focused_window
+    $mytoplevel configure -menu $::dialog_menubar
+    $mytoplevel configure -padx 0 -pady 0
+
+    frame $mytoplevel.buttonframe
+    pack $mytoplevel.buttonframe -side bottom -fill x -pady 2m
+    button $mytoplevel.buttonframe.send -text [_ "Send (Ctrl s)"] \
+        -command "::dialog_data::send $mytoplevel"
+    button $mytoplevel.buttonframe.ok -text [_ "OK (Ctrl t)"] \
+        -command "::dialog_data::ok $mytoplevel"
+    pack $mytoplevel.buttonframe.send -side left -expand 1
+    pack $mytoplevel.buttonframe.ok -side left -expand 1
+
+    text $mytoplevel.text -relief raised -bd 2 -height 40 -width 60 \
+        -yscrollcommand "$mytoplevel.scroll set"
+    scrollbar $mytoplevel.scroll -command "$mytoplevel.text yview"
+    pack $mytoplevel.scroll -side right -fill y
+    pack $mytoplevel.text -side left -fill both -expand 1
+    $mytoplevel.text insert end $stuff
+    focus $mytoplevel.text
+    bind $mytoplevel.text <Control-t> "::dialog_data::ok $mytoplevel"
+    bind $mytoplevel.text <Control-s> "::dialog_data::send $mytoplevel"
+}
diff --git a/pd/tcl/dialog_find.tcl b/pd/tcl/dialog_find.tcl
index c7a708ae5..443bec3a8 100644
--- a/pd/tcl/dialog_find.tcl
+++ b/pd/tcl/dialog_find.tcl
@@ -1,117 +1,182 @@
+# the find dialog panel is a bit unusual in that it is created directly by the
+# Tcl 'pd-gui'. Most dialog panels are created by sending a message to 'pd',
+# which then sends a message to 'pd-gui' to create the panel.
 
 package provide dialog_find 0.1
 
 package require pd_bindings
 
 namespace eval ::dialog_find:: {
+    variable find_in_toplevel ".pdwindow"
     # store the state of the "Match whole word only" check box
     variable wholeword_button 0
     # if the search hasn't changed, then the Find button sends "findagain"
     variable previous_wholeword_button 0
     variable previous_findstring ""
+    variable find_history {}
+    variable history_position 0
 
-    namespace export menu_dialog_find
+    namespace export pdtk_couldnotfind
 }
 
-# TODO make find panel as small as possible, being topmost means its findable
-# TODO (GNOME/Windows) find panel should retain focus after a find
-# TODO (Mac OS X) hide panel after success, but stay if the find was unsuccessful
+proc ::dialog_find::get_history {direction} {
+    variable find_history
+    variable history_position
+    
+    incr history_position $direction
+    if {$history_position < 0} {set history_position 0}
+    if {$history_position > [llength $find_history]} {
+        set history_position [llength $find_history]
+    }
+    .find.entry delete 0 end
+    .find.entry insert 0 [lindex $find_history end-[expr $history_position - 1]]
+}
 
+# mytoplevel isn't used here, but is kept for compatibility with other dialog ok procs
 proc ::dialog_find::ok {mytoplevel} {
+    variable find_in_window
     variable wholeword_button
     variable previous_wholeword_button
     variable previous_findstring
-    # find will be on top, so use the previous window that was on top
-    set search_window [lindex [wm stackorder .] end-1]
-    puts "search_window $search_window"
+    variable find_history
+
     set findstring [.find.entry get]
-    if {$findstring eq ""} {return}
-    if {$search_window eq ".pdwindow"} {
-        set matches [.pdwindow.text search -all -nocase -- $findstring 0.0]
+    if {$findstring eq ""} {
+        if {$::windowingsystem eq "aqua"} {bell}
+        return
+    }
+    if {$find_in_window eq ".pdwindow"} {
+        if {$::tcl_version < 8.5} {
+            # TODO implement in 8.4 style, without -all
+            set matches [.pdwindow.text search -nocase -- $findstring 0.0]
+        } else {
+            set matches [.pdwindow.text search -all -nocase -- $findstring 0.0]
+        }
         .pdwindow.text tag delete sel
-        foreach match $matches {
-            .pdwindow.text tag add sel $match "$match wordend"
+        if {[llength $matches] > 0} {
+            foreach match $matches {
+                .pdwindow.text tag add sel $match "$match wordend"
+            }
+            .pdwindow.text see [lindex $matches 0]
+            lappend find_history $findstring
         }
-        .pdwindow.text see [lindex $matches 0]
     } else {
         if {$findstring eq $previous_findstring \
                 && $wholeword_button == $previous_wholeword_button} {
-            pdsend "$search_window findagain"
+            pdsend "$find_in_window findagain"
         } else {
-            # TODO switch back to this for 0.43:
-            #pdsend "$search_window find $findstring $wholeword_button"
-            pdsend "$search_window find $findstring"
+            pdsend [concat $find_in_window find [pdtk_encodedialog $findstring] \
+                        $wholeword_button]
             set previous_findstring $findstring
             set previous_wholeword_button $wholeword_button
+            lappend find_history $findstring
         }
     }
+    if {$::windowingsystem eq "aqua"} {
+        # (Mac OS X) hide panel after success, but keep it if unsuccessful by
+        # having the couldnotfind proc reopen it
+        cancel $mytoplevel
+    } else {
+        # (GNOME/Windows) find panel should retain focus after a find
+        # (yes, a bit of a kludge)
+        after 100 "raise .find; focus .find.entry"
+    }
 }
 
+# mytoplevel isn't used here, but is kept for compatibility with other dialog cancel procs
 proc ::dialog_find::cancel {mytoplevel} {
     wm withdraw .find
 }
 
-proc ::dialog_find::set_canvas_to_search {mytoplevel} {
-    # TODO rewrite using global $::focused_window
+proc ::dialog_find::set_window_to_search {mytoplevel} {
+    variable find_in_window $mytoplevel
     if {[winfo exists .find.frame.targetlabel]} {
-        set focusedtoplevel [winfo toplevel [lindex [wm stackorder .] end]]
-        if {$focusedtoplevel eq ".find"} {
-            set focusedtoplevel [winfo toplevel [lindex [wm stackorder .] end-1]]
+        if {$find_in_window eq ".find"} {
+            set find_in_window [winfo toplevel [lindex [wm stackorder .] end-1]]
         }
-        if {$focusedtoplevel eq ".pdwindow"} {    
-            .find.frame.targetlabel configure -text [wm title .pdwindow]
-        } else {
-            foreach window $::menu_windowlist {
-                if {[lindex $window 1] eq $focusedtoplevel} {
-                    .find.frame.targetlabel configure -text [lindex $window 0]
-                }
-            }
+            # this has funny side effects in tcl 8.4 ???
+        if {$::tcl_version >= 8.5} {
+            wm transient .find $find_in_window
         }
+        .find.frame.targetlabel configure -text \
+            [lookup_windowname $find_in_window]
     }
 }
 
+proc ::dialog_find::pdtk_couldnotfind {mytoplevel} {
+    bell
+    ::pdwindow::error [format [_ "Couldn't find '%s' in %s"] \
+                   [.find.entry get] [lookup_windowname $mytoplevel] ]
+    if {$::windowingsystem eq "aqua"} {open_find_dialog $mytoplevel}
+}
+
 # the find panel is opened from the menu and key bindings
-proc ::dialog_find::menu_find_dialog {mytoplevel} {
+proc ::dialog_find::open_find_dialog {mytoplevel} {
     if {[winfo exists .find]} {
         wm deiconify .find
         raise .find
     } else {
         create_dialog $mytoplevel
     }
+    .find.entry selection range 0 end
 }
 
 proc ::dialog_find::create_dialog {mytoplevel} {
     toplevel .find -class DialogWindow
     wm title .find [_ "Find"]
     wm geometry .find =475x125+150+150
-    .find configure
-    if {$::windowingsystem eq "aqua"} {$mytoplevel configure -menu .menubar}
+    wm group .find .
+    wm resizable .find 0 0
+    wm transient .find
+    .find configure -menu $::dialog_menubar
+    .find configure -padx 10 -pady 5
     ::pd_bindings::dialog_bindings .find "find"
+    # sending these commands to the Find Dialog Panel should forward them to
+    # the currently focused patch
+    bind .find <$::modifier-Key-s> \
+        {menu_send $::focused_window menusave; break}
+    bind .find <$::modifier-Shift-Key-S> \
+        {menu_send $::focused_window menusaveas; break}
+    bind .find <$::modifier-Key-p> \
+        {menu_print $::focused_window; break}
     
     frame .find.frame
     pack .find.frame -side top -fill x -pady 1
     label .find.frame.searchin -text [_ "Search in"]
-    label .find.frame.targetlabel -font "TkTextFont 14" 
+    label .find.frame.targetlabel -text [_ "Pd window"]
     label .find.frame.for -text [_ "for:"]
     pack .find.frame.searchin .find.frame.targetlabel .find.frame.for -side left
     entry .find.entry -width 54 -font 18 -relief sunken \
-        -highlightthickness 3 -highlightcolor blue
-    focus .find.entry
+        -highlightthickness 1 -highlightcolor blue
     pack .find.entry -side top -padx 10
+
+    bind .find.entry <Up> "::dialog_find::get_history 1"
+    bind .find.entry <Down> "::dialog_find::get_history -1"
     
     checkbutton .find.wholeword -variable ::dialog_find::wholeword_button \
         -text [_ "Match whole word only"] -anchor w
     pack .find.wholeword -side top -padx 30 -pady 3 -fill x
     
     frame .find.buttonframe -background yellow
+    pack .find.buttonframe -side right -pady 3
+    if {$::windowingsystem eq "win32"} {
+        button .find.cancel -text [_ "Cancel"] -default normal -width 9 \
+            -command "::dialog_find::cancel $mytoplevel"
+        pack .find.cancel -side right -padx 6 -pady 3
+    }
     button .find.button -text [_ "Find"] -default active -width 9 \
         -command "::dialog_find::ok $mytoplevel"
+    pack .find.button -side right -padx 6 -pady 3
     if {$::windowingsystem eq "x11"} {
         button .find.close -text [_ "Close"] -default normal -width 9 \
             -command "::dialog_find::cancel $mytoplevel"
-        pack .find.buttonframe .find.button .find.close -side right -padx 10 -pady 3
-    } else {
-        pack .find.buttonframe .find.button -side right -padx 10 -pady 3
+        pack .find.close -side right -padx 6 -pady 3
     }
-    ::dialog_find::set_canvas_to_search $mytoplevel
+    # on Mac OS X, the buttons shouldn't get Tab/keyboard focus
+    if {$::windowingsystem eq "aqua"} {
+        .find.wholeword configure -takefocus 0
+        .find.button configure -takefocus 0
+    }
+    ::dialog_find::set_window_to_search $mytoplevel
+    focus .find.entry
 }
diff --git a/pd/tcl/dialog_font.tcl b/pd/tcl/dialog_font.tcl
index 578d155ea..fce160003 100644
--- a/pd/tcl/dialog_font.tcl
+++ b/pd/tcl/dialog_font.tcl
@@ -7,17 +7,17 @@ namespace eval ::dialog_font:: {
     variable whichstretch 1
     variable canvaswindow
     variable sizes {8 10 12 16 24 36}
-    variable gfxstub
     
     namespace export pdtk_canvas_dofont
 }
 
 # TODO this should use the pd_font_$size fonts created in pd-gui.tcl
+# TODO change pdtk_canvas_dofont to pdtk_font_dialog here and g_editor.c
 
 # TODO this should really be changed on the C side so that it doesn't have to
 # work around gfxstub/x_gui.c.  The gfxstub stuff assumes that there are
 # multiple panels, for properties panels like this, its much easier to use if
-# there is a single properties panel that adjusts based on which CanvasWindow
+# there is a single properties panel that adjusts based on which PatchWindow
 # has focus
 
 proc ::dialog_font::apply {mytoplevel myfontsize} {
@@ -30,44 +30,41 @@ proc ::dialog_font::apply {mytoplevel myfontsize} {
     }
 }
 
-proc ::dialog_font::cancel {mygfxstub} {
-    if {$mygfxstub ne ".pdwindow"} {
-        pdsend "$mygfxstub cancel"
+proc ::dialog_font::cancel {gfxstub} {
+    if {$gfxstub ne ".pdwindow"} {
+        pdsend "$gfxstub cancel"
     }
     destroy .font
 }
 
-proc ::dialog_font::ok {mygfxstub} {
+proc ::dialog_font::ok {gfxstub} {
     variable fontsize
-    ::dialog_font::apply $mygfxstub $fontsize
-    ::dialog_font::cancel $mygfxstub
+    apply $gfxstub $fontsize
+    cancel $gfxstub
 }
 
 proc ::dialog_font::update_font_dialog {mytoplevel} {
-    set ::dialog_font::canvaswindow $mytoplevel
-    if {$mytoplevel eq ".pdwindow"} {
-        set windowname [_ "Pd window"]
-    } else {
-        set windowname [lookup_windowname $mytoplevel]
-    }
+    variable canvaswindow $mytoplevel
     if {[winfo exists .font]} {
-        wm title .font [format [_ "%s Font"] $windowname]
+        wm title .font [format [_ "%s Font"] [lookup_windowname $mytoplevel]]
     }
 }
 
 proc ::dialog_font::arrow_fontchange {change} {
     variable sizes
-    set position [expr [lsearch $sizes $::dialog_font::fontsize] + $change]
+    variable fontsize
+    variable canvaswindow
+    set position [expr [lsearch $sizes $fontsize] + $change]
     if {$position < 0} {set position 0}
     set max [llength $sizes]
     if {$position >= $max} {set position [expr $max-1]}
-    set ::dialog_font::fontsize [lindex $sizes $position]
-    ::dialog_font::apply $::dialog_font::canvaswindow $::dialog_font::fontsize
+    set fontsize [lindex $sizes $position]
+    ::dialog_font::apply $canvaswindow $fontsize
 }
 
 # this should be called pdtk_font_dialog like the rest of the panels, but it
 # is called from the C side, so we'll leave it be
-proc ::dialog_font::pdtk_canvas_dofont {mygfxstub initsize} {
+proc ::dialog_font::pdtk_canvas_dofont {gfxstub initsize} {
     variable fontsize $initsize
     variable whichstretch 1
     variable stretchval 100
@@ -77,28 +74,34 @@ proc ::dialog_font::pdtk_canvas_dofont {mygfxstub initsize} {
         # the gfxstub stuff expects multiple font windows, we only have one,
         # so kill the new gfxstub requests as the come in.  We'll save the
         # original gfxstub for when the font panel gets closed
-        pdsend "$mygfxstub cancel"
+        pdsend "$gfxstub cancel"
     } else {
-        create_dialog $mygfxstub
+        create_dialog $gfxstub
     }
 }
 
-proc ::dialog_font::create_dialog {mygfxstub} {
-    variable gfxstub $mygfxstub
+proc ::dialog_font::create_dialog {gfxstub} {
     toplevel .font -class DialogWindow
-    if {$::windowingsystem eq "aqua"} {.font configure -menu .menubar}
+    .font configure -menu $::dialog_menubar
+    .font configure -padx 10 -pady 5
+    wm group .font .
+    wm resizable .font 0 0
+    wm transient .font $::focused_window
     ::pd_bindings::dialog_bindings .font "font"
-    # replace standard bindings to work around the gfxstub stuff
-    bind .font <KeyPress-Escape> "::dialog_font::cancel $mygfxstub"
-    bind .font <KeyPress-Return> "::dialog_font::ok $mygfxstub"
-    bind .font <$::pd_bindings::modifier-Key-w> "::dialog_font::cancel $mygfxstub"
+    # replace standard bindings to work around the gfxstub stuff and use
+    # break to prevent the close window command from going to other bindings.
+    # .font won't exist anymore, so it'll cause errors down the line...
+    bind .font <KeyPress-Return> "::dialog_font::ok $gfxstub; break"
+    bind .font <KeyPress-Escape> "::dialog_font::cancel $gfxstub; break"
+    bind .font <$::modifier-Key-w> "::dialog_font::cancel $gfxstub; break"
+    wm protocol .font WM_DELETE_WINDOW "dialog_font::cancel $gfxstub"
     bind .font <Up> "::dialog_font::arrow_fontchange -1"
     bind .font <Down> "::dialog_font::arrow_fontchange 1"
     
     frame .font.buttonframe
     pack .font.buttonframe -side bottom -fill x -pady 2m
     button .font.buttonframe.ok -text [_ "OK"] \
-        -command "::dialog_font::ok $mygfxstub"
+        -command "::dialog_font::ok $gfxstub"
     pack .font.buttonframe.ok -side left -expand 1
     
     labelframe .font.fontsize -text [_ "Font Size"] -padx 5 -pady 4 -borderwidth 1 \
diff --git a/pd/tcl/dialog_gatom.tcl b/pd/tcl/dialog_gatom.tcl
index b59751bfb..3b30a1a9e 100644
--- a/pd/tcl/dialog_gatom.tcl
+++ b/pd/tcl/dialog_gatom.tcl
@@ -39,13 +39,13 @@ proc ::dialog_gatom::apply {mytoplevel} {
     global gatomlabel_radio
     
     pdsend "$mytoplevel param \
-                 [$mytoplevel.width.entry get] \
-                 [$mytoplevel.limits.lower.entry get] \
-                 [$mytoplevel.limits.upper.entry get] \
-                 [::dialog_gatom::escape [$mytoplevel.gatomlabel.name.entry get]] \
-                 $gatomlabel_radio($mytoplevel) \
-                 [::dialog_gatom::escape [$mytoplevel.s_r.send.entry get]] \
-                 [::dialog_gatom::escape [$mytoplevel.s_r.receive.entry get]]"
+        [$mytoplevel.width.entry get] \
+        [$mytoplevel.limits.lower.entry get] \
+        [$mytoplevel.limits.upper.entry get] \
+        [::dialog_gatom::escape [$mytoplevel.gatomlabel.name.entry get]] \
+        $gatomlabel_radio($mytoplevel) \
+        [::dialog_gatom::escape [$mytoplevel.s_r.receive.entry get]] \
+        [::dialog_gatom::escape [$mytoplevel.s_r.send.entry get]]"
 }
 
 proc ::dialog_gatom::cancel {mytoplevel} {
@@ -60,7 +60,7 @@ proc ::dialog_gatom::ok {mytoplevel} {
 # set up the panel with the info from pd
 proc ::dialog_gatom::pdtk_gatom_dialog {mytoplevel initwidth initlower initupper \
                                      initgatomlabel_radio \
-                                     initgatomlabel initsend initreceive} {
+                                     initgatomlabel initreceive initsend} {
     global gatomlabel_radio
     set gatomlabel_radio($mytoplevel) $initgatomlabel_radio
 
@@ -75,14 +75,17 @@ proc ::dialog_gatom::pdtk_gatom_dialog {mytoplevel initwidth initlower initupper
     $mytoplevel.limits.lower.entry insert 0 $initlower
     $mytoplevel.limits.upper.entry insert 0 $initupper
     if {$initgatomlabel ne "-"} {
-        $mytoplevel.gatomlabel.name.entry insert 0 $initgatomlabel
+        $mytoplevel.gatomlabel.name.entry insert 0 \
+            [::dialog_gatom::unescape $initgatomlabel]
     }
     set gatomlabel_radio($mytoplevel) $initgatomlabel_radio
         if {$initsend ne "-"} {
-        $mytoplevel.s_r.send.entry insert 0 $initsend
+        $mytoplevel.s_r.send.entry insert 0 \
+            [::dialog_gatom::unescape $initsend]
     }
     if {$initreceive ne "-"} {
-        $mytoplevel.s_r.receive.entry insert 0 $initreceive
+        $mytoplevel.s_r.receive.entry insert 0 \
+            [::dialog_gatom::unescape $initreceive]
     }
 }
 
@@ -91,7 +94,11 @@ proc ::dialog_gatom::create_dialog {mytoplevel} {
 
     toplevel $mytoplevel -class DialogWindow
     wm title $mytoplevel [_ "Atom Box Properties"]
-    if {$::windowingsystem eq "aqua"} {$mytoplevel configure -menu .menubar}
+    wm group $mytoplevel .
+    wm resizable $mytoplevel 0 0
+    wm transient $mytoplevel $::focused_window
+    $mytoplevel configure -menu $::dialog_menubar
+    $mytoplevel configure -padx 0 -pady 0
     ::pd_bindings::dialog_bindings $mytoplevel "gatom"
 
     frame $mytoplevel.width -height 7
@@ -149,16 +156,18 @@ proc ::dialog_gatom::create_dialog {mytoplevel} {
     pack $mytoplevel.s_r.receive.entry $mytoplevel.s_r.receive.label -side right
     
     frame $mytoplevel.buttonframe -pady 5
-    pack $mytoplevel.buttonframe -side top -fill x -pady 2m
+    pack $mytoplevel.buttonframe -side top -fill x -expand 1 -pady 2m
     button $mytoplevel.buttonframe.cancel -text [_ "Cancel"] \
         -command "::dialog_gatom::cancel $mytoplevel"
-    pack $mytoplevel.buttonframe.cancel -side left -expand 1
-    button $mytoplevel.buttonframe.apply -text [_ "Apply"] \
-        -command "::dialog_gatom::apply $mytoplevel"
-    pack $mytoplevel.buttonframe.apply -side left -expand 1
+    pack $mytoplevel.buttonframe.cancel -side left -expand 1 -fill x -padx 10
+    if {$::windowingsystem ne "aqua"} {
+        button $mytoplevel.buttonframe.apply -text [_ "Apply"] \
+            -command "::dialog_gatom::apply $mytoplevel"
+    pack $mytoplevel.buttonframe.apply -side left -expand 1 -fill x -padx 10
+    }
     button $mytoplevel.buttonframe.ok -text [_ "OK"] \
         -command "::dialog_gatom::ok $mytoplevel"
-    pack $mytoplevel.buttonframe.ok -side left -expand 1
+    pack $mytoplevel.buttonframe.ok -side left -expand 1 -fill x -padx 10
 
     $mytoplevel.width.entry select from 0
     $mytoplevel.width.entry select adjust end
diff --git a/pd/tcl/dialog_iemgui.tcl b/pd/tcl/dialog_iemgui.tcl
index 34ed4ccb4..5ad6cad58 100644
--- a/pd/tcl/dialog_iemgui.tcl
+++ b/pd/tcl/dialog_iemgui.tcl
@@ -73,7 +73,7 @@ proc ::dialog_iemgui::sched_rng {mytoplevel} {
             $mytoplevel.rng.max_ent configure -textvariable $var_iemgui_max_rng
             $mytoplevel.rng.min_ent configure -textvariable $var_iemgui_min_rng }
         if {[eval concat $$var_iemgui_max_rng] < $define_min_flashhold} {
-            set $var_iemgui_max_rng $iemgui_define_min_flashhold
+            set $var_iemgui_max_rng $define_min_flashhold
             $mytoplevel.rng.max_ent configure -textvariable $var_iemgui_max_rng
         }
         if {[eval concat $$var_iemgui_min_rng] < $define_min_flashbreak} {
@@ -252,7 +252,7 @@ proc ::dialog_iemgui::toggle_font {mytoplevel gn_f} {
         1 { set current_font "Helvetica" }
         2 { set current_font "Times" }
     }
-    set current_font_spec "{$current_font} 12 $::font_weight"
+    set current_font_spec "{$current_font} 16 $::font_weight"
     
     $mytoplevel.label.fontpopup_label configure -text $current_font \
         -font $current_font_spec
@@ -362,7 +362,11 @@ proc ::dialog_iemgui::apply {mytoplevel} {
     set hhhsnd [unspace_text $hhhsnd]
     set hhhrcv [unspace_text $hhhrcv]
     set hhhgui_nam [unspace_text $hhhgui_nam]
-    
+
+# make sure the offset boxes have a value
+    if {[eval concat $$var_iemgui_gn_dx] eq ""} {set $var_iemgui_gn_dx 0}
+    if {[eval concat $$var_iemgui_gn_dy] eq ""} {set $var_iemgui_gn_dy 0}
+
     pdsend [concat $mytoplevel dialog \
             [eval concat $$var_iemgui_wdt] \
             [eval concat $$var_iemgui_hgt] \
@@ -496,12 +500,16 @@ proc ::dialog_iemgui::pdtk_iemgui_dialog {mytoplevel mainheader dim_header \
     
     toplevel $mytoplevel -class DialogWindow
     wm title $mytoplevel [format [_ "%s Properties"] $mainheader]
-    if {$::windowingsystem eq "aqua"} {$mytoplevel configure -menu .menubar}
+    wm group $mytoplevel .
+    wm resizable $mytoplevel 0 0
+    wm transient $mytoplevel $::focused_window
+    $mytoplevel configure -menu $::dialog_menubar
+    $mytoplevel configure -padx 0 -pady 0
     ::pd_bindings::dialog_bindings $mytoplevel "iemgui"
 
     frame $mytoplevel.dim
     pack $mytoplevel.dim -side top
-    label $mytoplevel.dim.head -text $dim_header
+    label $mytoplevel.dim.head -text [_ $dim_header]
     label $mytoplevel.dim.w_lab -text [_ $wdt_label] -width 6
     entry $mytoplevel.dim.w_ent -textvariable $var_iemgui_wdt -width 5
     label $mytoplevel.dim.dummy1 -text " " -width 10
@@ -514,7 +522,7 @@ proc ::dialog_iemgui::pdtk_iemgui_dialog {mytoplevel mainheader dim_header \
     
     frame $mytoplevel.rng
     pack $mytoplevel.rng -side top
-    label $mytoplevel.rng.head -text $rng_header
+    label $mytoplevel.rng.head -text [_ $rng_header]
     label $mytoplevel.rng.min_lab -text [_ $min_rng_label] -width 6
     entry $mytoplevel.rng.min_ent -textvariable $var_iemgui_min_rng -width 9
     label $mytoplevel.rng.dummy1 -text " " -width 1
@@ -544,21 +552,19 @@ proc ::dialog_iemgui::pdtk_iemgui_dialog {mytoplevel mainheader dim_header \
             -command "::dialog_iemgui::lilo $mytoplevel" }
     if {[eval concat $$var_iemgui_loadbang] == 0} {
         button $mytoplevel.para.lb -text [_ "No init"] \
-            -width [::msgcat::mcmax "No init"] \
             -command "::dialog_iemgui::lb $mytoplevel" }
     if {[eval concat $$var_iemgui_loadbang] == 1} {
         button $mytoplevel.para.lb -text [_ "Save"] \
-            -width [::msgcat::mcmax "Save"] \
             -command "::dialog_iemgui::lb $mytoplevel" }
     label $mytoplevel.para.num_lab -text [_ $num_label] -width 9
     entry $mytoplevel.para.num_ent -textvariable $var_iemgui_num -width 4
 
     if {[eval concat $$var_iemgui_steady] == 0} {
         button $mytoplevel.para.stdy_jmp -command "::dialog_iemgui::stdy_jmp $mytoplevel" \
-            -text [_ "Jump on click"] -width [::msgcat::mcmax "Jump on click"] }
+            -text [_ "Jump on click"] }
     if {[eval concat $$var_iemgui_steady] == 1} {
         button $mytoplevel.para.stdy_jmp -command "::dialog_iemgui::stdy_jmp $mytoplevel" \
-            -text [_ "Steady on click"] -width [::msgcat::mcmax "Steady on click"] }
+            -text [_ "Steady on click"] }
     if {[eval concat $$var_iemgui_lin0_log1] >= 0} {
         pack $mytoplevel.para.lilo -side left -expand 1}
     if {[eval concat $$var_iemgui_loadbang] >= 0} {
@@ -574,18 +580,22 @@ proc ::dialog_iemgui::pdtk_iemgui_dialog {mytoplevel mainheader dim_header \
     labelframe $mytoplevel.s_r -borderwidth 1 -pady 4 -text [_ "Messages"]
     pack $mytoplevel.s_r -side top -fill x -ipadx 5
     frame $mytoplevel.s_r.send
-    pack $mytoplevel.s_r.send -side top
-    label $mytoplevel.s_r.send.lab -text [_ "Send symbol:"] -width 12  -justify right
+    pack $mytoplevel.s_r.send -side top -padx 4 -fill x -expand 1
+    label $mytoplevel.s_r.send.lab -text [_ "Send symbol:"] -justify left
     entry $mytoplevel.s_r.send.ent -textvariable $var_iemgui_snd -width 22
     if { $snd ne "nosndno" } {
-        pack $mytoplevel.s_r.send.lab $mytoplevel.s_r.send.ent -side left}
+        pack $mytoplevel.s_r.send.lab $mytoplevel.s_r.send.ent -side left \
+            -fill x -expand 1
+    }
     
     frame $mytoplevel.s_r.receive
-    pack $mytoplevel.s_r.receive -side top
-    label $mytoplevel.s_r.receive.lab -text [_ "Receive symbol:"] -width 12 -justify right
+    pack $mytoplevel.s_r.receive -side top -padx 4 -fill x -expand 1
+    label $mytoplevel.s_r.receive.lab -text [_ "Receive symbol:"] -justify left
     entry $mytoplevel.s_r.receive.ent -textvariable $var_iemgui_rcv -width 22
     if { $rcv ne "norcvno" } {
-        pack $mytoplevel.s_r.receive.lab $mytoplevel.s_r.receive.ent -side left}
+        pack $mytoplevel.s_r.receive.lab $mytoplevel.s_r.receive.ent -side left \
+            -fill x -expand 1
+    }
     
     # get the current font name from the int given from C-space (gn_f)
     set current_font $::font_family
@@ -599,42 +609,40 @@ proc ::dialog_iemgui::pdtk_iemgui_dialog {mytoplevel mainheader dim_header \
     
     labelframe $mytoplevel.label -borderwidth 1 -text [_ "Label"] -pady 4
     pack $mytoplevel.label -side top -fill x
-    entry $mytoplevel.label.name_entry -textvariable $var_iemgui_gui_nam -width 30 \
-        -font [list $current_font 12 $::font_weight]
+    entry $mytoplevel.label.name_entry -textvariable $var_iemgui_gui_nam \
+        -width 30 -font [list $current_font 12 $::font_weight]
     pack $mytoplevel.label.name_entry -side top -expand yes -fill both -padx 5
     
     frame $mytoplevel.label.xy -padx 27 -pady 1
     pack $mytoplevel.label.xy -side top
-    label $mytoplevel.label.xy.x_lab -text [_ "X offset"] \
-        -width [::msgcat::mcmax "X offset"]
+    label $mytoplevel.label.xy.x_lab -text [_ "X offset"]
     entry $mytoplevel.label.xy.x_entry -textvariable $var_iemgui_gn_dx -width 5
     label $mytoplevel.label.xy.dummy1 -text " " -width 2
-    label $mytoplevel.label.xy.y_lab -text [_ "Y offset"] \
-        -width [::msgcat::mcmax "Y offset"]
+    label $mytoplevel.label.xy.y_lab -text [_ "Y offset"]
     entry $mytoplevel.label.xy.y_entry -textvariable $var_iemgui_gn_dy -width 5
     pack $mytoplevel.label.xy.x_lab $mytoplevel.label.xy.x_entry $mytoplevel.label.xy.dummy1 \
         $mytoplevel.label.xy.y_lab $mytoplevel.label.xy.y_entry -side left -anchor e
     
-    label $mytoplevel.label.fontpopup_label -text $current_font \
-        -relief groove -font [list $current_font 12 $::font_weight] -padx 5
-    pack $mytoplevel.label.fontpopup_label -side left -anchor w -expand yes -fill x
-    label $mytoplevel.label.fontsize_label -text [_ "Size:"] \
-        -width [::msgcat::mcmax "Size:"]
+    button $mytoplevel.label.fontpopup_label -text $current_font \
+        -font [list $current_font 16 $::font_weight]
+    pack $mytoplevel.label.fontpopup_label -side left -anchor w \
+        -expand 1 -fill x -padx 5
+    label $mytoplevel.label.fontsize_label -text [_ "Size:"]
     entry $mytoplevel.label.fontsize_entry -textvariable $var_iemgui_gn_fs -width 5
     pack $mytoplevel.label.fontsize_entry $mytoplevel.label.fontsize_label \
         -side right -anchor e -padx 5 -pady 5
     menu $mytoplevel.popup
     $mytoplevel.popup add command \
         -label $::font_family \
-        -font [format {{%s} 12 %s} $::font_family $::font_weight] \
+        -font [format {{%s} 16 %s} $::font_family $::font_weight] \
         -command "::dialog_iemgui::toggle_font $mytoplevel 0" 
     $mytoplevel.popup add command \
         -label "Helvetica" \
-        -font [format {Helvetica 12 %s} $::font_weight] \
+        -font [format {Helvetica 16 %s} $::font_weight] \
         -command "::dialog_iemgui::toggle_font $mytoplevel 1" 
     $mytoplevel.popup add command \
         -label "Times" \
-        -font [format {Times 12 %s} $::font_weight] \
+        -font [format {Times 16 %s} $::font_weight] \
         -command "::dialog_iemgui::toggle_font $mytoplevel 2" 
     bind $mytoplevel.label.fontpopup_label <Button> \
         [list tk_popup $mytoplevel.popup %X %Y]
@@ -648,14 +656,11 @@ proc ::dialog_iemgui::pdtk_iemgui_dialog {mytoplevel mainheader dim_header \
     frame $mytoplevel.colors.select
     pack $mytoplevel.colors.select -side top
     radiobutton $mytoplevel.colors.select.radio0 -value 0 -variable \
-        $var_iemgui_l2_f1_b0 -text [_ "Background"] -justify left \
-        -width [::msgcat::mcmax "Background"]
+        $var_iemgui_l2_f1_b0 -text [_ "Background"] -justify left
     radiobutton $mytoplevel.colors.select.radio1 -value 1 -variable \
-        $var_iemgui_l2_f1_b0 -text [_ "Front"] -justify left \
-        -width [::msgcat::mcmax "Front"]
+        $var_iemgui_l2_f1_b0 -text [_ "Front"] -justify left
     radiobutton $mytoplevel.colors.select.radio2 -value 2 -variable \
-        $var_iemgui_l2_f1_b0 -text [_ "Label"] -justify left \
-        -width [::msgcat::mcmax "Label"]
+        $var_iemgui_l2_f1_b0 -text [_ "Label"] -justify left
     if { [eval concat $$var_iemgui_fcol] >= 0 } {
         pack $mytoplevel.colors.select.radio0 $mytoplevel.colors.select.radio1 \
             $mytoplevel.colors.select.radio2 -side left
@@ -666,7 +671,6 @@ proc ::dialog_iemgui::pdtk_iemgui_dialog {mytoplevel mainheader dim_header \
     frame $mytoplevel.colors.sections
     pack $mytoplevel.colors.sections -side top
     button $mytoplevel.colors.sections.but -text [_ "Compose color"] \
-        -width [::msgcat::mcmax "Compose color"] \
         -command "::dialog_iemgui::choose_col_bkfrlb $mytoplevel"
     pack $mytoplevel.colors.sections.but -side left -anchor w -padx 10 -pady 5 \
         -expand yes -fill x
@@ -686,7 +690,6 @@ proc ::dialog_iemgui::pdtk_iemgui_dialog {mytoplevel mainheader dim_header \
             -font [list $current_font 12 $::font_weight] -padx 2 -pady 2 -relief ridge
     }
     label $mytoplevel.colors.sections.lb_bk -text [_ "Test label"] \
-        -width [::msgcat::mcmax "Test label"] \
         -background [format "#%6.6x" [eval concat $$var_iemgui_bcol]] \
         -activebackground [format "#%6.6x" [eval concat $$var_iemgui_bcol]] \
         -foreground [format "#%6.6x" [eval concat $$var_iemgui_lcol]] \
@@ -744,26 +747,18 @@ proc ::dialog_iemgui::pdtk_iemgui_dialog {mytoplevel mainheader dim_header \
         $mytoplevel.colors.r3.c8 $mytoplevel.colors.r3.c9 -side left
     
     frame $mytoplevel.cao -pady 10
-    pack $mytoplevel.cao -side top
-    button $mytoplevel.cao.cancel -text [_ "Cancel"] -width 6 \
+    pack $mytoplevel.cao -side top -expand 1 -fill x
+    button $mytoplevel.cao.cancel -text [_ "Cancel"] \
         -command "::dialog_iemgui::cancel $mytoplevel"
-    label $mytoplevel.cao.dummy1 -text "" -width 3
-    button $mytoplevel.cao.apply -text [_ "Apply"] -width 6 \
-        -command "::dialog_iemgui::apply $mytoplevel"
-    label $mytoplevel.cao.dummy2 -text "" -width 3
-    button $mytoplevel.cao.ok -text [_ "OK"] -width 6 \
-        -command "::dialog_iemgui::ok $mytoplevel"
-    pack $mytoplevel.cao.cancel $mytoplevel.cao.dummy1 -side left
-    pack $mytoplevel.cao.apply $mytoplevel.cao.dummy2 -side left
-    pack $mytoplevel.cao.ok -side left
-    
-    if {[info tclversion] < 8.4} {
-        bind $mytoplevel <Key-Tab> {tkTabToWindow [tk_focusNext %W]}
-        bind $mytoplevel <<PrevWindow>> {tkTabToWindow [tk_focusPrev %W]}
-    } else {
-        bind $mytoplevel <Key-Tab> {tk::TabToWindow [tk_focusNext %W]}
-        bind $mytoplevel <<PrevWindow>> {tk::TabToWindow [tk_focusPrev %W]}
+    pack $mytoplevel.cao.cancel -side left -padx 10 -expand 1 -fill x
+    if {$::windowingsystem ne "aqua"} {
+        button $mytoplevel.cao.apply -text [_ "Apply"] \
+            -command "::dialog_iemgui::apply $mytoplevel"
+        pack $mytoplevel.cao.apply -side left -padx 10 -expand 1 -fill x
     }
+    button $mytoplevel.cao.ok -text [_ "OK"] \
+        -command "::dialog_iemgui::ok $mytoplevel"
+    pack $mytoplevel.cao.ok -side left -padx 10 -expand 1 -fill x
     
     $mytoplevel.dim.w_ent select from 0
     $mytoplevel.dim.w_ent select adjust end
diff --git a/pd/tcl/dialog_message.tcl b/pd/tcl/dialog_message.tcl
new file mode 100644
index 000000000..107f1095c
--- /dev/null
+++ b/pd/tcl/dialog_message.tcl
@@ -0,0 +1,85 @@
+# the message dialog panel is a bit unusual in that it is created directly by
+# the Tcl 'pd-gui'. Most dialog panels are created by sending a message to
+# 'pd', which then sends a message to 'pd-gui' to create the panel.  This is
+# similar to the Find dialog panel.
+
+package provide dialog_message 0.1
+
+package require pd_bindings
+
+namespace eval ::dialog_message:: {
+    variable message_history {"pd dsp 1"}
+    variable history_position 0
+
+    namespace export open_message_dialog
+}
+
+proc ::dialog_message::get_history {direction} {
+    variable message_history
+    variable history_position
+    
+    incr history_position $direction
+    if {$history_position < 0} {set history_position 0}
+    if {$history_position > [llength $message_history]} {
+        set history_position [llength $message_history]
+    }
+    .message.f.entry delete 0 end
+    .message.f.entry insert 0 \
+        [lindex $message_history end-[expr $history_position - 1]]
+}
+
+# mytoplevel isn't used here, but is kept for compatibility with other dialog ok procs
+proc ::dialog_message::ok {mytoplevel} {
+    variable message_history
+    set message [.message.f.entry get]
+    if {$message ne ""} {
+        pdsend $message
+        lappend message_history $message
+        .message.f.entry delete 0 end
+    }
+}
+
+# mytoplevel isn't used here, but is kept for compatibility with other dialog cancel procs
+proc ::dialog_message::cancel {mytoplevel} {
+    wm withdraw .message
+}
+
+# the message panel is opened from the menu and key bindings
+proc ::dialog_message::open_message_dialog {mytoplevel} {
+    if {[winfo exists .message]} {
+        wm deiconify .message
+        raise .message
+    } else {
+        create_dialog $mytoplevel
+    }
+}
+
+proc ::dialog_message::create_dialog {mytoplevel} {
+    toplevel .message -class DialogWindow
+    wm group .message .
+    wm transient .message
+    wm title .message [_ "Send a Pd message"]
+    wm geometry .message =400x80+150+150
+    wm resizable .message 1 0
+    wm minsize .message 250 80
+    .message configure -menu $::dialog_menubar
+    .message configure -padx 10 -pady 5
+    ::pd_bindings::dialog_bindings .message "message"
+    # not all Tcl/Tk versions or platforms support -topmost, so catch the error
+    catch {wm attributes $id -topmost 1}
+
+    # TODO this should use something like 'dialogfont' for the font
+    frame .message.f
+    pack .message.f -side top -fill x -expand 1
+    entry .message.f.entry -width 54 -font {Helvetica 18} -relief sunken \
+        -highlightthickness 1 -highlightcolor blue
+    label .message.f.semicolon -text ";" -font {Helvetica 24}
+    pack .message.f.semicolon -side left
+    pack .message.f.entry -side left -padx 10 -fill x -expand 1
+    focus .message.f.entry
+    label .message.label -text [_ "(use arrow keys for history)"]
+    pack .message.label -side bottom
+
+    bind .message.f.entry <Up> "::dialog_message::get_history 1"
+    bind .message.f.entry <Down> "::dialog_message::get_history -1"
+}
diff --git a/pd/tcl/dialog_midi.tcl b/pd/tcl/dialog_midi.tcl
index d85546657..450d29388 100644
--- a/pd/tcl/dialog_midi.tcl
+++ b/pd/tcl/dialog_midi.tcl
@@ -2,6 +2,7 @@ package provide dialog_midi 0.1
 
 namespace eval ::dialog_midi:: {
     namespace export pdtk_midi_dialog
+    namespace export pdtk_alsa_midi_dialog
 }
 
 # TODO this panel really needs some reworking, it works but the code is
@@ -81,10 +82,16 @@ proc ::dialog_midi::pdtk_midi_dialog {id indev1 indev2 indev3 indev4 \
     set midi_alsain [llength $midi_indevlist]
     set midi_alsaout [llength $midi_outdevlist]
 
-    toplevel $id
+    toplevel $id -class DialogWindow
     wm title $id [_ "MIDI Settings"]
-    if {$::windowingsystem eq "aqua"} {$id configure -menu .menubar}
+    wm group $id .
+    wm resizable $id 0 0
+    wm transient $id
+    $id configure -menu $::dialog_menubar
+    $id configure -padx 10 -pady 5
     ::pd_bindings::dialog_bindings $id "midi"
+    # not all Tcl/Tk versions or platforms support -topmost, so catch the error
+    catch {wm attributes $id -topmost 1}
 
     frame $id.buttonframe
     pack $id.buttonframe -side bottom -fill x -pady 2m
@@ -165,7 +172,7 @@ proc ::dialog_midi::pdtk_midi_dialog {id indev1 indev2 indev3 indev4 \
     }
 
         # output device 3
-    if {$longform && [llength $midi_midi_outdevlist] > 3} {
+    if {$longform && [llength $midi_outdevlist] > 3} {
         frame $id.out3f
         pack $id.out3f -side top
         label $id.out3f.l1 -text [_ "Output device 3:"]
@@ -176,7 +183,7 @@ proc ::dialog_midi::pdtk_midi_dialog {id indev1 indev2 indev3 indev4 \
     }
 
         # output device 4
-    if {$longform && [llength $midi_midi_outdevlist] > 4} {
+    if {$longform && [llength $midi_outdevlist] > 4} {
         frame $id.out4f
         pack $id.out4f -side top
         label $id.out4f.l1 -text [_ "Output device 4:"]
diff --git a/pd/tcl/dialog_path.tcl b/pd/tcl/dialog_path.tcl
new file mode 100644
index 000000000..40a306bae
--- /dev/null
+++ b/pd/tcl/dialog_path.tcl
@@ -0,0 +1,70 @@
+
+package provide dialog_path 0.1
+
+namespace eval ::dialog_path:: {
+    variable use_standard_extensions_button 1
+    variable verbose_button 0
+
+    namespace export pdtk_path_dialog
+}
+
+############ pdtk_path_dialog -- run a path dialog #########
+
+# set up the panel with the info from pd
+proc ::dialog_path::pdtk_path_dialog {mytoplevel extrapath verbose} {
+    global use_standard_extensions_button
+    global verbose_button
+    set use_standard_extensions_button $extrapath
+    set verbose_button $verbose
+
+    if {[winfo exists $mytoplevel]} {
+        wm deiconify $mytoplevel
+        raise $mytoplevel
+    } else {
+        create_dialog $mytoplevel
+    }
+}
+
+proc ::dialog_path::create_dialog {mytoplevel} {
+
+    scrollboxwindow::make $mytoplevel $::sys_searchpath \
+        dialog_path::add dialog_path::edit dialog_path::commit \
+        [_ "Pd search path for objects, help, fonts, and other files"] \
+        400 300
+    
+    frame $mytoplevel.extraframe
+    pack $mytoplevel.extraframe -side bottom -pady 2m
+    checkbutton $mytoplevel.extraframe.extra -text [_ "Use standard extensions"] \
+        -variable use_standard_extensions_button -anchor w 
+    checkbutton $mytoplevel.extraframe.verbose -text [_ "Verbose"] \
+        -variable verbose_button -anchor w 
+    pack $mytoplevel.extraframe.extra $mytoplevel.extraframe.verbose \
+        -side left -expand 1
+}
+
+
+
+############ pdtk_path_dialog -- dialog window for search path #########
+proc ::dialog_path::choosePath { currentpath title } {
+    if {$currentpath == ""} {
+        set currentpath "~"
+    }
+    return [tk_chooseDirectory -initialdir $currentpath -title $title]
+}
+
+proc ::dialog_path::add {} {
+    return [::dialog_path::choosePath "" {Add a new path}]
+}
+
+proc ::dialog_path::edit { currentpath } {
+    return [::dialog_path::choosePath $currentpath "Edit existing path \[$currentpath\]"]
+}
+
+proc ::dialog_path::commit { new_path } {
+    global use_standard_extensions_button
+    global verbose_button
+
+    set ::sys_searchpath $new_path
+    pdsend "pd path-dialog $use_standard_extensions_button $verbose_button $::sys_searchpath"
+}
+
diff --git a/pd/tcl/dialog_startup.tcl b/pd/tcl/dialog_startup.tcl
new file mode 100644
index 000000000..52c5f6474
--- /dev/null
+++ b/pd/tcl/dialog_startup.tcl
@@ -0,0 +1,96 @@
+
+package provide dialog_startup 0.1
+
+package require scrollboxwindow
+
+namespace eval dialog_startup {
+    variable defeatrt_flag 0
+    
+    namespace export pdtk_startup_dialog
+}
+
+########## pdtk_startup_dialog -- dialog window for startup options #########
+# Create a simple modal window with an entry widget
+# for editing/adding a startup command 
+# (the next-best-thing to in-place editing)
+proc ::dialog_startup::chooseCommand { prompt initialValue } {
+    global cmd
+    set cmd $initialValue
+    
+    toplevel .inputbox
+    wm title .inputbox $prompt
+    wm group .inputbox .
+    wm minsize .inputbox 450 30
+    wm resizable .inputbox 0 0
+    wm geom .inputbox "450x30"
+    # not all Tcl/Tk versions or platforms support -topmost, so catch the error
+    catch {wm attributes $mytoplevel -topmost 1}
+
+    button .inputbox.button -text [_ "OK"] -command { destroy .inputbox } \
+        -width [::msgcat::mcmax [_ "OK"]]
+
+    entry .inputbox.entry -width 50 -textvariable cmd 
+    pack .inputbox.button -side right
+    bind .inputbox.entry <KeyPress-Return> { destroy .inputbox }
+    bind .inputbox.entry <KeyPress-Escape> { destroy .inputbox }
+    pack .inputbox.entry -side right -expand 1 -fill x -padx 2m
+
+    focus .inputbox.entry
+
+    raise .inputbox
+    wm transient .inputbox
+    grab .inputbox
+    tkwait window .inputbox
+
+    return $cmd
+}
+
+proc ::dialog_startup::add {} {
+    return [chooseCommand [_ "Add new library"] ""]
+}
+
+proc ::dialog_startup::edit { current_library } {
+    return [chooseCommand [_ "Edit library"] $current_library]
+}
+
+proc ::dialog_startup::commit { new_startup } {
+    variable defeatrt_button
+    set ::startup_libraries $new_startup
+
+    pdsend "pd startup-dialog $defeatrt_button [pdtk_encodedialog $::startup_flags] $::startup_libraries"
+}
+
+# set up the panel with the info from pd
+proc ::dialog_startup::pdtk_startup_dialog {mytoplevel defeatrt flags} {
+    variable defeatrt_button $defeatrt
+    if {$flags ne ""} {variable ::startup_flags $flags}
+    
+    if {[winfo exists $mytoplevel]} {
+        wm deiconify $mytoplevel
+        raise $mytoplevel
+    } else {
+        create_dialog $mytoplevel
+    }
+}
+
+proc ::dialog_startup::create_dialog {mytoplevel} {
+    ::scrollboxwindow::make $mytoplevel $::startup_libraries \
+        dialog_startup::add dialog_startup::edit dialog_startup::commit \
+        [_ "Pd libraries to load on startup"] \
+        400 300
+
+    label $mytoplevel.entryname -text [_ "Startup flags:"]
+    entry $mytoplevel.entry -textvariable ::startup_flags -width 60
+    pack $mytoplevel.entryname $mytoplevel.entry -side left
+    pack $mytoplevel.entry -side right -padx 2m -fill x -expand 1
+
+    frame $mytoplevel.defeatrtframe
+    pack $mytoplevel.defeatrtframe -side bottom -fill x -pady 2m
+    if {$::windowingsystem ne "win32"} {
+        checkbutton $mytoplevel.defeatrtframe.defeatrt -anchor w \
+            -text [_ "Defeat real-time scheduling"] \
+            -variable ::dialog_startup::defeatrt_button
+        pack $mytoplevel.defeatrtframe.defeatrt -side left
+    }
+}
+
diff --git a/pd/tcl/helpbrowser.tcl b/pd/tcl/helpbrowser.tcl
new file mode 100644
index 000000000..bcec1fc56
--- /dev/null
+++ b/pd/tcl/helpbrowser.tcl
@@ -0,0 +1,272 @@
+
+package provide helpbrowser 0.1
+
+namespace eval ::helpbrowser:: {
+    variable libdirlist
+    variable helplist
+    variable reference_count
+    variable reference_paths
+    variable doctypes "*.{pd,pat,mxb,mxt,help,txt,htm,html,pdf}"
+
+    namespace export open_helpbrowser
+}
+
+# TODO remove the doc_ prefix on procs where its not needed
+# TODO rename .help_browser to .helpbrowser
+# TODO enter and up/down/left/right arrow key bindings for nav
+
+################## help browser and support functions #########################
+proc ::helpbrowser::open_helpbrowser {} {
+    if { [winfo exists .help_browser.frame] } {
+        wm deiconify .help_browser
+        raise .help_browser
+    } else {
+        toplevel .help_browser -class HelpBrowser
+        wm group .help_browser .
+        wm transient .help_browser
+        wm title .help_browser [_ "Help Browser"]
+        bind .help_browser <$::modifier-Key-w> "wm withdraw .help_browser"
+
+        if {$::windowingsystem eq "aqua"} {
+            .help_browser configure -menu $::dialog_menubar
+        }
+
+        wm resizable .help_browser 0 0
+        frame .help_browser.frame
+        pack .help_browser.frame -side top -fill both
+        build_references
+        make_rootlistbox .help_browser.frame
+    }
+}
+
+# make the root listbox of the help browser using the pre-built lists
+proc ::helpbrowser::make_rootlistbox {base} {
+    variable libdirlist
+    variable helplist
+    # exportselection 0 looks good, but selection gets easily out-of-sync
+	set current_listbox [listbox "[set b $base.root]" -yscrollcommand "$b-scroll set" \
+                             -highlightbackground white -highlightthickness 5 \
+                             -highlightcolor "#D6E5FC" -selectborderwidth 0 \
+                             -height 20 -width 23 -exportselection 0 -bd 0]
+	pack $current_listbox [scrollbar "$b-scroll" -command [list $current_listbox yview]] \
+        -side left -fill both -expand 1
+    foreach item [concat [lsort [concat $libdirlist $helplist]]] {
+		$current_listbox insert end $item
+	}
+	bind $current_listbox <Button-1> \
+        [list ::helpbrowser::root_navigate %W %x %y]
+    bind $current_listbox <Key-Return> \
+        [list ::helpbrowser::root_navigate %W %x %y]
+	bind $current_listbox <Double-ButtonRelease-1> \
+        [list ::helpbrowser::root_doubleclick %W %x %y]
+	bind $current_listbox <$::modifier-Key-o> \
+        [list ::helpbrowser::root_doubleclick %W %x %y]
+}
+
+# navigate into a library/directory from the root
+proc ::helpbrowser::root_navigate {window x y} {
+    variable reference_paths
+    if {[set item [$window get [$window index "@$x,$y"]]] eq {}} {
+        return
+    }
+    set filename $reference_paths($item)
+    if {[file isdirectory $filename]} {
+        make_liblistbox [winfo parent $window] $filename
+    }
+}
+
+# double-click action to open the folder
+proc ::helpbrowser::root_doubleclick {window x y} {
+    variable reference_paths
+    if {[set listname [$window get [$window index "@$x,$y"]]] eq {}} {
+        return
+    }
+    set dir [file dirname $reference_paths($listname)]
+    set filename [file tail $reference_paths($listname)]
+    ::pdwindow::verbose 0 "menu_doc_open $dir $filename"
+    if { [catch {menu_doc_open $dir $filename} fid] } {
+        ::pdwindow::error "Could not open $dir/$filename\n"
+    }
+}
+
+# make the listbox to show the first level contents of a libdir
+proc ::helpbrowser::make_liblistbox {base dir} {
+    variable doctypes
+    catch { eval destroy [lrange [winfo children $base] 2 end] } errorMessage
+    # exportselection 0 looks good, but selection gets easily out-of-sync
+	set current_listbox [listbox "[set b $base.listbox0]" -yscrollcommand "$b-scroll set" \
+                             -highlightbackground white -highlightthickness 5 \
+                             -highlightcolor "#D6E5FC" -selectborderwidth 0 \
+                             -height 20 -width 23 -exportselection 0 -bd 0]
+	pack $current_listbox [scrollbar "$b-scroll" -command [list $current_listbox yview]] \
+        -side left -fill both -expand 1
+	foreach item [lsort -dictionary [glob -directory $dir -nocomplain -types {d} -- *]] {
+        if {[glob -directory $item -nocomplain -types {f} -- $doctypes] ne "" ||
+            [glob -directory $item -nocomplain -types {d} -- *] ne ""} {
+            $current_listbox insert end "[file tail $item]/"
+        }
+    }
+    foreach item [lsort -dictionary [glob -directory $dir -nocomplain -types {f} -- \
+                                         *-{help,meta}.pd]]  {
+        $current_listbox insert end [file tail $item]
+	}
+    $current_listbox insert end "___________________________"
+    foreach item [lsort -dictionary [glob -directory $dir -nocomplain -types {f} -- \
+                                         *.txt]]  {
+        $current_listbox insert end [file tail $item]
+	}
+	bind $current_listbox <Button-1> \
+        [list ::helpbrowser::dir_navigate $dir 1 %W %x %y]
+	bind $current_listbox <Double-ButtonRelease-1> \
+        [list ::helpbrowser::dir_doubleclick $dir 1 %W %x %y]
+	bind $current_listbox <Key-Return> \
+        [list ::helpbrowser::dir_doubleclick $dir 1 %W %x %y]
+}
+
+proc ::helpbrowser::doc_make_listbox {base dir count} {
+    variable doctypes
+    # check for [file readable]?
+	# requires Tcl 8.5 but probably deals with special chars better:
+	#        destroy {*}[lrange [winfo children $base] [expr {2 * $count}] end]
+	if { [catch { eval destroy [lrange [winfo children $base] \
+									[expr { 2 * $count }] end] } errorMessage] } {
+		::pdwindow::error "doc_make_listbox: error listing $dir\n"
+	}
+    # exportselection 0 looks good, but selection gets easily out-of-sync
+	set current_listbox [listbox "[set b "$base.listbox$count"]-list" \
+                             -yscrollcommand "$b-scroll set" \
+                             -highlightbackground white -highlightthickness 5 \
+                             -highlightcolor "#D6E5FC" -selectborderwidth 0 \
+                             -height 20 -width 23 -exportselection 0 -bd 0]
+	pack $current_listbox [scrollbar "$b-scroll" -command "$current_listbox yview"] \
+        -side left -fill both -expand 1
+	foreach item [lsort -dictionary [glob -directory $dir -nocomplain -types {d} -- *]] {
+		$current_listbox insert end "[file tail $item]/"
+    }
+	foreach item [lsort -dictionary [glob -directory $dir -nocomplain -types {f} -- \
+                                         $doctypes]]  {
+		$current_listbox insert end [file tail $item]
+	}
+	bind $current_listbox <Button-1> \
+        "::helpbrowser::dir_navigate {$dir} $count %W %x %y"
+    bind $current_listbox <Key-Right> \
+        "::helpbrowser::dir_navigate {$dir} $count %W %x %y"
+	bind $current_listbox <Double-ButtonRelease-1> \
+        "::helpbrowser::dir_doubleclick {$dir} $count %W %x %y"
+    bind $current_listbox <Key-Return> \
+        "::helpbrowser::dir_doubleclick {$dir} $count %W %x %y"
+}
+
+# navigate into an actual directory
+proc ::helpbrowser::dir_navigate {dir count window x y} {
+    if {[set newdir [$window get [$window index "@$x,$y"]]] eq {}} {
+        return
+    }
+    set dir_to_open [file join $dir $newdir]
+    if {[file isdirectory $dir_to_open]} {
+        doc_make_listbox [winfo parent $window] $dir_to_open [incr count]
+    }
+}
+
+proc ::helpbrowser::dir_doubleclick {dir count window x y} {
+    if {[set filename [$window get [$window index "@$x,$y"]]] eq {}} {
+        return
+    }
+    if { [catch {menu_doc_open $dir $filename} fid] } {
+        ::pdwindow::error "Could not open $dir/$filename\n"
+    }
+}
+
+proc ::helpbrowser::rightclickmenu {dir count window x y} {
+    if {[set filename [$window get [$window index "@$x,$y"]]] eq {}} {
+        return
+    }
+    if { [catch {menu_doc_open $dir $filename} fid] } {
+        ::pdwindow::error "Could not open $dir/$filename\n"
+    }
+}
+
+#------------------------------------------------------------------------------#
+# build help browser trees
+
+# TODO check file timestamp against timestamp of when tree was built
+
+proc ::helpbrowser::findfiles {basedir pattern} {
+    set basedir [string trimright [file join [file normalize $basedir] { }]]
+    set filelist {}
+
+    # Look in the current directory for matching files, -type {f r}
+    # means ony readable normal files are looked at, -nocomplain stops
+    # an error being thrown if the returned list is empty
+    foreach filename [glob -nocomplain -type {f r} -path $basedir $pattern] {
+        lappend filelist $filename
+    }
+
+    foreach dirName [glob -nocomplain -type {d  r} -path $basedir *] {
+        set subdirlist [findfiles $dirName $pattern]
+        if { [llength $subdirlist] > 0 } {
+            foreach subdirfile $subdirlist {
+                lappend filelist $subdirfile
+            }
+        }
+    }
+    return $filelist
+}
+
+proc ::helpbrowser::add_entry {reflist entry} {
+    variable libdirlist
+    variable helplist
+    variable reference_paths
+    variable reference_count
+    set entryname [file tail $entry]
+    # if we are checking libdirs, then check to see if there is already a
+    # libdir with that name that has been discovered in the path.  If so, dump
+    # a warning. The trailing slash on $entryname is added below when
+    # $entryname is a dir
+    if {$reflist eq "libdirlist" && [lsearch -exact $libdirlist $entryname/] > -1} {
+        ::pdwindow::error "WARNING: duplicate '$entryname' library found!\n"
+        ::pdwindow::error "  '$reference_paths($entryname/)' is active\n"
+        ::pdwindow::error "  '$entry' is duplicate\n"
+        incr reference_count($entryname)
+        append entryname "/ ($reference_count($entryname))"
+    } else {
+        set reference_count($entryname) 1
+        if {[file isdirectory $entry]} {
+            append entryname "/"
+        }
+    }
+    lappend $reflist $entryname
+    set reference_paths($entryname) $entry
+}
+
+proc ::helpbrowser::build_references {} {
+    variable libdirlist {" Pure Data/" "-----------------------"}
+    variable helplist {}
+    variable reference_count
+    variable reference_paths
+
+    array set reference_count {}
+    array set reference_paths [list \
+                                   " Pure Data/" $::sys_libdir/doc \
+                                   "-----------------------" "" \
+                                  ]
+    foreach pathdir [concat $::sys_searchpath $::sys_staticpath] {
+        if { ! [file isdirectory $pathdir]} {continue}
+        # Fix the directory name, this ensures the directory name is in the
+        # native format for the platform and contains a final directory seperator
+        set dir [string trimright [file join [file normalize $pathdir] { }]]
+        ## find the libdirs
+        foreach filename [glob -nocomplain -type d -path $dir "*"] {
+            add_entry libdirlist $filename
+        }
+        ## find the stray help patches
+        foreach filename [glob -nocomplain -type f -path $dir "*-help.pd"] {
+            add_entry helplist $filename
+        }
+    }
+}
+
+
+
+
+ 	  	 
diff --git a/pd/tcl/opt_parser.tcl b/pd/tcl/opt_parser.tcl
index d304e045c..c34baf6d9 100644
--- a/pd/tcl/opt_parser.tcl
+++ b/pd/tcl/opt_parser.tcl
@@ -3,35 +3,44 @@ package provide opt_parser 0.1
 namespace eval opt_parser {
     # list of option vars (keys are long option names)
     variable optlist
+    # option behavior <set|lappend>
+    variable optbehavior
     variable optprefix {-}
 }
 
 proc opt_parser::init {optdata} {
     variable optlist
-    array unset optlist
-    array set optlist {}
+    variable optbehavior
+    array unset optlist ; array set optlist {}
+    array unset optbehavior ; array set optbehavior {}
     foreach item $optdata {
-        foreach {longname varlist} $item {
-            if {[llength $varlist] < 1} {
-                return -code error "usage: init { {optname {var1 var2 ...}} ... }"
+        foreach {optName behavior varlist} $item {
+            if {[llength $varlist] < 1 || [lsearch -exact {set lappend} $behavior] == -1} {
+                return -code error "usage: init { {optname <set|lappend> {var1 var2 ...}} ... }"
             }
-            set optlist($longname) $varlist
+            set optlist($optName) $varlist
+            set optbehavior($optName) $behavior
         }
     }
 }
 
 proc opt_parser::get_options {argv {opts {}}} {
-    set ignore_unknown_flags 0
+    # second argument are internal options
+    # (like 'ignore_unknown_flags <0|1>')
     foreach {k v} $opts {set $k $v}
+    set ignore_unknown_flags 0
 
     variable optlist
+    variable optbehavior
     variable optprefix
 
     # zero all the options 1st var
     foreach optName [array names optlist] {
         uplevel [list set [lindex $optlist($optName) 0] 0]
-        for {set i 1} {$i < [llength $optlist($optName)]} {incr i} {
-            uplevel [list set [lindex $optlist($optName) $i] [list]]
+        if {$optbehavior($optName) == {lappend}} {
+            for {set i 1} {$i < [llength $optlist($optName)]} {incr i} {
+                uplevel [list set [lindex $optlist($optName) $i] [list]]
+            }
         }
     }
 
@@ -41,16 +50,15 @@ proc opt_parser::get_options {argv {opts {}}} {
     set argc [llength $argv]
     for {set i 0} {$i < $argc} {} {
         # get i-th arg
-        set argv_i [lindex $argv $i]
+        set optName [lindex $argv $i]
         incr i
 
         # if it's not an option, stop here, and add to residualArgs
-        if {![regexp ^$optprefix $argv_i]} {
-            lappend residualArgs $argv_i
+        if {![regexp ^$optprefix $optName]} {
+            lappend residualArgs $optName
             continue
         }
 
-        set optName [regsub ^$optprefix $argv_i {}]
         if {[info exists optlist($optName)]} {
             set varlist $optlist($optName)
             uplevel [list set [lindex $optlist($optName) 0] 1]
@@ -59,9 +67,9 @@ proc opt_parser::get_options {argv {opts {}}} {
             while {$n_required_opt_args > 0} {
                 incr n_required_opt_args -1
                 if {$i >= $argc} {
-                    return -code error "not enough arguments for option $optprefix$optName"
+                    return -code error "not enough arguments for option $optName"
                 }
-                uplevel [list lappend [lindex $varlist $j] [lindex $argv $i]]
+                uplevel [list $optbehavior($optName) [lindex $varlist $j] [lindex $argv $i]]
                 incr j
                 incr i
             }
@@ -70,7 +78,7 @@ proc opt_parser::get_options {argv {opts {}}} {
                 lappend residualArgs $argv_i
                 continue
             } else {
-                return -code error "unknown option: $optprefix$optName"
+                return -code error "unknown option: $optName"
             }
         }
     }
diff --git a/pd/tcl/pd-gui.tcl b/pd/tcl/pd-gui.tcl
index 6dfe16632..d379c459d 100644
--- a/pd/tcl/pd-gui.tcl
+++ b/pd/tcl/pd-gui.tcl
@@ -8,15 +8,18 @@
 # "." automatically gets a window, we don't want it.  Withdraw it before doing
 # anything else, so that we don't get the automatic window flashing for a
 # second while pd loads.
-wm withdraw . 
-
-puts -------------------------------pd-gui.tcl-----------------------------------
+if { [catch {wm withdraw .} fid] } { exit 2 }
 
 package require Tcl 8.3
 package require Tk
-package require Tk
-if {[tk windowingsystem] ne "win32"} {package require msgcat}
-# TODO figure out msgcat issue on Windows
+#package require tile
+## replace Tk widgets with Ttk widgets on 8.5
+#namespace import -force ttk::*
+
+package require msgcat
+# TODO create a constructor in each package to create things at startup, that
+#  way they can be easily be modified by startup scripts
+# TODO create alt-Enter/Cmd-I binding to bring up Properties panels
 
 # Pd's packages are stored in the same directory as the main script (pd-gui.tcl)
 set auto_path [linsert $auto_path 0 [file dirname [info script]]]
@@ -27,26 +30,58 @@ package require pdwindow
 package require dialog_array
 package require dialog_audio
 package require dialog_canvas
+package require dialog_data
 package require dialog_font
 package require dialog_gatom
 package require dialog_iemgui
+package require dialog_message
 package require dialog_midi
+package require dialog_path
+package require dialog_startup
+package require helpbrowser
+package require pd_menucommands
+package require opt_parser
 package require pdtk_canvas
 package require pdtk_text
+package require pdtk_textwindow
 # TODO eliminate this kludge:
 package require wheredoesthisgo
+package require pd_guiprefs
+
+#------------------------------------------------------------------------------#
+# import functions into the global namespace
+
+# gui preferences
+namespace import ::pd_guiprefs::init
+namespace import ::pd_guiprefs::update_recentfiles
+namespace import ::pd_guiprefs::write_recentfiles
+# make global since they are used throughout    
+namespace import ::pd_menucommands::* 
 
 # import into the global namespace for backwards compatibility
 namespace import ::pd_connect::pdsend
 namespace import ::pdwindow::pdtk_post
+namespace import ::pdwindow::pdtk_pd_dio
+namespace import ::pdwindow::pdtk_pd_dsp
+namespace import ::pdwindow::pdtk_pd_meters
+namespace import ::pdtk_canvas::pdtk_canvas_popup
+namespace import ::pdtk_canvas::pdtk_canvas_editmode
+namespace import ::pdtk_canvas::pdtk_canvas_getscroll
+namespace import ::pdtk_canvas::pdtk_canvas_setparents
+namespace import ::pdtk_canvas::pdtk_canvas_reflecttitle
+namespace import ::pdtk_canvas::pdtk_canvas_menuclose
 namespace import ::dialog_array::pdtk_array_dialog
 namespace import ::dialog_audio::pdtk_audio_dialog
 namespace import ::dialog_canvas::pdtk_canvas_dialog
+namespace import ::dialog_data::pdtk_data_dialog
+namespace import ::dialog_find::pdtk_couldnotfind
 namespace import ::dialog_font::pdtk_canvas_dofont
 namespace import ::dialog_gatom::pdtk_gatom_dialog
 namespace import ::dialog_iemgui::pdtk_iemgui_dialog
 namespace import ::dialog_midi::pdtk_midi_dialog
 namespace import ::dialog_midi::pdtk_alsa_midi_dialog
+namespace import ::dialog_path::pdtk_path_dialog
+namespace import ::dialog_startup::pdtk_startup_dialog
 
 # hack - these should be better handled in the C code
 namespace import ::dialog_array::pdtk_array_listview_new
@@ -57,10 +92,16 @@ namespace import ::dialog_array::pdtk_array_listview_closeWindow
 #------------------------------------------------------------------------------#
 # global variables
 
+# this is a wide array of global variables that are used throughout the GUI.
+# they can be used in plugins to check the status of various things since they
+# should all have been properly initialized by the time startup plugins are
+# loaded.
+
 set PD_MAJOR_VERSION 0
 set PD_MINOR_VERSION 0
 set PD_BUGFIX_VERSION 0
 set PD_TEST_VERSION ""
+set done_init 0
 
 set TCL_MAJOR_VERSION 0
 set TCL_MINOR_VERSION 0
@@ -69,8 +110,13 @@ set TCL_BUGFIX_VERSION 0
 # for testing which platform we are running on ("aqua", "win32", or "x11")
 set windowingsystem ""
 
-# variable for vwait so that 'pd-gui' will timeout if 'pd' never shows up
-set wait4pd "init"
+# args about how much and where to log
+set loglevel 2
+set stderr 0
+
+# connection between 'pd' and 'pd-gui'
+set host ""
+set port 0
 
 # canvas font, received from pd in pdtk_pd_startup, set in s_main.c
 set font_family "courier"
@@ -78,45 +124,103 @@ set font_weight "normal"
 # sizes of chars for each of the Pd fixed font sizes:
 #  fontsize  width(pixels)  height(pixels)
 set font_fixed_metrics {
-    8 5 10
-    9 6 11
-    10 6 13
-    12 7 15
+    8 6 11
+    9 6 12
+    10 7 13
+    12 9 16
     14 8 17
     16 10 20
     18 11 22
-    24 14 30
+    24 15 25
     30 18 37
-    36 22 45
+    36 25 45
 }
+set font_measured_metrics {}
 
 # root path to lib of Pd's files, see s_main.c for more info
 set sys_libdir {}
 # root path where the pd-gui.tcl GUI script is located
 set sys_guidir {}
-
-set audioapi_list {}
-set midiapi_list {}
+# user-specified search path for objects, help, fonts, etc.
+set sys_searchpath {}
+# hard-coded search patch for objects, help, plugins, etc.
+set sys_staticpath {}
+# the path to the folder where the current plugin is being loaded from
+set current_plugin_loadpath {}
+# a list of plugins that were loaded
+set loaded_plugins {}
+# list of command line flags set at startup
+set startup_flags {}
+# list of libraries loaded on startup
+set startup_libraries {}
+# start dirs for new files and open panels
+set filenewdir [pwd]
+set fileopendir [pwd]
+
+
+# lists of audio/midi devices and APIs for prefs dialogs
+set audio_apilist {}
+set audio_indevlist {}
+set audio_outdevlist {}
+set midi_apilist {}
+set midi_indevlist {}
+set midi_outdevlist {}
 set pd_whichapi 0
 set pd_whichmidiapi 0
 
 # current state of the DSP
 set dsp 0
+# state of the peak meters in the Pd window
+set meters 0
 # the toplevel window that currently is on top and has focus
 set focused_window .
-# TODO figure out how to get all windows into the menu_windowlist
-# store list of parent windows for Window menu
-set menu_windowlist {}
-# store that last 10 files that were opened
+# store that last 5 files that were opened
 set recentfiles_list {}
-set total_recentfiles 10
-# keep track of the location of popup menu for CanvasWindows
-set popup_xpix 0
-set popup_ypix 0
+set total_recentfiles 5
+# keep track of the location of popup menu for PatchWindows, in canvas coords
+set popup_xcanvas 0
+set popup_ycanvas 0
+# modifier for key commands (Ctrl/Control on most platforms, Cmd/Mod1 on MacOSX)
+set modifier ""
+# current state of the Edit Mode menu item
+set editmode_button 0
+
 
 ## per toplevel/patch data
-# store editmode for each open canvas, starting with a blank array
-array set editmode {}
+# window location modifiers
+set menubarsize 0           ;# Mac OS X and other platforms have a menubar on top
+set windowframex 0      ;# different platforms have different window frames
+set windowframey 0      ;# different platforms have different window frames
+# patch properties
+array set editmode {}   ;# store editmode for each open patch canvas
+array set editingtext {};# if an obj, msg, or comment is being edited, per patch
+array set loaded {}     ;# store whether a patch has completed loading
+array set xscrollable {};# keep track of whether the scrollbars are present
+array set yscrollable {}
+# patch window tree, these might contain patch IDs without a mapped toplevel 
+array set windowname {}    ;# window names based on mytoplevel IDs
+array set childwindows {}  ;# all child windows based on mytoplevel IDs
+array set parentwindows {} ;# topmost parent window ID based on mytoplevel IDs
+
+# variables for holding the menubar to allow for configuration by plugins
+set ::pdwindow_menubar ".menubar"
+set ::patch_menubar   ".menubar"
+set ::dialog_menubar   ""
+
+# minimum size of the canvas window of a patch
+set canvas_minwidth 50
+set canvas_minheight 20
+
+# undo states
+set ::undo_action "no"
+set ::redo_action "no"
+set ::undo_toplevel "."
+
+
+namespace eval ::pdgui:: {
+    variable scriptname [ file normalize [ info script ] ]
+}
+
 
 #------------------------------------------------------------------------------#
 # coding style
@@ -129,17 +233,23 @@ array set editmode {}
 # - if a menu_* proc opens a dialog panel, that proc is called menu_*_dialog
 # - use "eq/ne" for string comparison, NOT "==/!=" (http://wiki.tcl.tk/15323)
 #
+#
 ## Names for Common Variables
 #----------------------------
-#
 # variables named after the Tk widgets they represent
+#   $window     = any kind of Tk widget that can be a Tk 'window'
 #   $mytoplevel = a window id made by a 'toplevel' command
-#   $mygfxstub = a window id made by a 'toplevel' command via gfxstub/x_gui.c
-#   $menubar = the 'menu' attached to each 'toplevel'
-#   $mymenu = 'menu' attached to the menubar
-#   $menuitem = 'menu' item
-#   $mycanvas = 'canvas'
-#   $canvasitem = 'canvas' item
+#   $gfxstub  = a 'toplevel' window id for dialogs made in gfxstub/x_gui.c
+#   $menubar    = the 'menu' attached to each 'toplevel'
+#   $mymenu     = 'menu' attached to the menubar, like the File menu
+#   $tkcanvas   = a Tk 'canvas', which is the root of each patch
+#
+#
+## Dialog Panel Types
+#----------------------------
+#   global (only one):   find, sendmessage, prefs, helpbrowser
+#   per-canvas:          font, canvas properties (created with a message from pd)
+#   per object:          gatom, iemgui, array, data structures (created with a message from pd)
 #
 #
 ## Prefix Names for procs
@@ -150,18 +260,6 @@ array set editmode {}
 # ------------------------------------------------------------------------------
 # init functions
 
-proc set_pd_version {versionstring} {
-    regexp -- {.*([0-9])\.([0-9]+)[\.\-]([0-9]+)([^0-9]?.*)} $versionstring \
-        wholematch \
-        ::PD_MAJOR_VERSION ::PD_MINOR_VERSION ::PD_BUGFIX_VERSION ::PD_TEST_VERSION
-}
-
-proc set_tcl_version {} {
-    regexp {([0-9])\.([0-9])\.([0-9]+)} [info patchlevel] \
-        wholematch \
-        ::TCL_MAJOR_VERSION ::TCL_MINOR_VERSION ::TCL_BUGFIX_VERSION
-}
-
 # root paths to find Pd's files where they are installed
 proc set_pd_paths {} {
     set ::sys_guidir [file normalize [file dirname [info script]]]
@@ -175,6 +273,8 @@ proc init_for_platform {} {
 
     switch -- $::windowingsystem {
         "x11" {
+            set ::modifier "Control"
+            option add *PatchWindow*Canvas.background "white" startupFile
             # add control to show/hide hidden files in the open panel (load
             # the tk_getOpenFile dialog once, otherwise it will not work)
             catch {tk_getOpenFile -with-invalid-argument} 
@@ -188,8 +288,32 @@ proc init_for_platform {} {
                      [list [_ "Max Patch Files"]   {.pat} ] \
                      [list [_ "Max Text Files"]    {.mxt} ] \
                     ]
+            # some platforms have a menubar on the top, so place below them
+            set ::menubarsize 0
+            # Tk handles the window placement differently on each
+            # platform. With X11, the x,y placement refers to the window
+            # frame's upper left corner. http://wiki.tcl.tk/11502
+            set ::windowframex 3
+            set ::windowframey 53
+			# TODO add wm iconphoto/iconbitmap here if it makes sense
+            # mouse cursors for all the different modes
+            set ::cursor_runmode_nothing "left_ptr"
+            set ::cursor_runmode_clickme "arrow"
+            set ::cursor_runmode_thicken "sb_v_double_arrow"
+            set ::cursor_runmode_addpoint "plus"
+            set ::cursor_editmode_nothing "hand2"
+            set ::cursor_editmode_connect "circle"
+            set ::cursor_editmode_disconnect "X_cursor"
+            set ::cursor_editmode_resize "sb_h_double_arrow"
         }
         "aqua" {
+            set ::modifier "Mod1"
+            option add *DialogWindow*background "#E8E8E8" startupFile
+            option add *DialogWindow*Entry.highlightBackground "#E8E8E8" startupFile
+            option add *DialogWindow*Button.highlightBackground "#E8E8E8" startupFile
+            option add *DialogWindow*Entry.background "white" startupFile
+            # Mac OS X needs a menubar all the time
+            set ::dialog_menubar ".menubar"
             # set file types that open/save recognize
             set ::filetypes \
                 [list \
@@ -197,10 +321,34 @@ proc init_for_platform {} {
                      [list [_ "Pd Files"]               {.pd}  ] \
                      [list [_ "Max Patch Files (.pat)"] {.pat} ] \
                      [list [_ "Max Text Files (.mxt)"]  {.mxt} ] \
-                    ]
+                ]
+            # some platforms have a menubar on the top, so place below them
+            set ::menubarsize 22
+            # Tk handles the window placement differently on each platform, on
+            # Mac OS X, the x,y placement refers to the content window's upper
+            # left corner (not of the window frame) http://wiki.tcl.tk/11502
+            set ::windowframex 0
+            set ::windowframey 0
+            # mouse cursors for all the different modes
+            set ::cursor_runmode_nothing "arrow"
+            set ::cursor_runmode_clickme "center_ptr"
+            set ::cursor_runmode_thicken "sb_v_double_arrow"
+            set ::cursor_runmode_addpoint "plus"
+            set ::cursor_editmode_nothing "hand2"
+            set ::cursor_editmode_connect "circle"
+            set ::cursor_editmode_disconnect "X_cursor"
+            set ::cursor_editmode_resize "sb_h_double_arrow"
         }
         "win32" {
+            set ::modifier "Control"
+            option add *PatchWindow*Canvas.background "white" startupFile
+            # fix menu font size on Windows with tk scaling = 1
             font create menufont -family Tahoma -size -11
+            option add *Menu.font menufont startupFile
+            option add *HelpBrowser*font menufont startupFile
+            option add *DialogWindow*font menufont startupFile
+            option add *PdWindow*font menufont startupFile
+            option add *ErrorDialog*font menufont startupFile
             # set file types that open/save recognize
             set ::filetypes \
                 [list \
@@ -209,6 +357,25 @@ proc init_for_platform {} {
                      [list [_ "Max Patch Files"]   {.pat} ] \
                      [list [_ "Max Text Files"]    {.mxt} ] \
                     ]
+            # some platforms have a menubar on the top, so place below them
+            set ::menubarsize 0
+            # Tk handles the window placement differently on each platform, on
+            # Mac OS X, the x,y placement refers to the content window's upper
+            # left corner. http://wiki.tcl.tk/11502 
+            # TODO this probably needs a script layer: http://wiki.tcl.tk/11291
+            set ::windowframex 0
+            set ::windowframey 0
+            # TODO use 'winico' package for full, hicolor icon support
+            wm iconbitmap . -default [file join $::sys_guidir pd.ico]
+            # mouse cursors for all the different modes
+            set ::cursor_runmode_nothing "right_ptr"
+            set ::cursor_runmode_clickme "arrow"
+            set ::cursor_runmode_thicken "sb_v_double_arrow"
+            set ::cursor_runmode_addpoint "plus"
+            set ::cursor_editmode_nothing "hand2"
+            set ::cursor_editmode_connect "circle"
+            set ::cursor_editmode_disconnect "X_cursor"
+            set ::cursor_editmode_resize "sb_h_double_arrow"
         }
     }
 }
@@ -217,26 +384,32 @@ proc init_for_platform {} {
 # locale handling
 
 # official GNU gettext msgcat shortcut
-if {[tk windowingsystem] ne "win32"} {
-    proc _ {s} {return [::msgcat::mc $s]}
-} else {
-    proc _ {s} {return $s}
-}
+proc _ {s} {return [::msgcat::mc $s]}
 
 proc load_locale {} {
-    if {[tk windowingsystem] ne "win32"} {
-        ::msgcat::mcload [file join [file dirname [info script]] .. po]
+    # on any UNIX-like environment, Tcl should automatically use LANG, LC_ALL,
+    # etc. otherwise we need to dig it up.  Mac OS X only uses LANG, etc. from
+    # the Terminal, and Windows doesn't have LANG, etc unless you manually set
+    # it up yourself.  Windows apps don't use the locale env vars usually.
+    if {$::tcl_platform(os) eq "Darwin" && ! [info exists ::env(LANG)]} {
+        # http://thread.gmane.org/gmane.comp.lang.tcl.mac/5215
+        # http://thread.gmane.org/gmane.comp.lang.tcl.mac/6433
+        if {![catch "exec defaults read com.apple.dock loc" lang]} {
+            ::msgcat::mclocale $lang
+        } elseif {![catch "exec defaults read NSGlobalDomain AppleLocale" lang]} {
+            ::msgcat::mclocale $lang
+        }
+    } elseif {$::tcl_platform(platform) eq "windows"} {
+        # using LANG on Windows is useful for easy debugging
+        if {[info exists ::env(LANG)] && $::env(LANG) ne "C" && $::env(LANG) ne ""} {  
+            ::msgcat::mclocale $::env(LANG)
+        } elseif {![catch {package require registry}]} {
+            ::msgcat::mclocale [string tolower \
+                                    [string range \
+                                         [registry get {HKEY_CURRENT_USER\Control Panel\International} sLanguage] 0 1] ]
+        }
     }
-
-    # for Windows
-    #set locale "en"  ;# Use whatever is right for your app
-    #if {[catch {package require registry}]} {
-    #        tk_messageBox -icon error -message "Could not get locale from registry"
-    #} else {
-    #    set locale [string tolower \
-    #        [string range \
-    #        [registry get {HKEY_CURRENT_USER\Control Panel\International} sLanguage] 0 1] ]
-    #}
+    ::msgcat::mcload [file join [file dirname [info script]] .. po]
 
     ##--moo: force default system and stdio encoding to UTF-8
     encoding system utf-8
@@ -258,32 +431,32 @@ proc get_font_for_size {size} {
 # always do a good job of choosing in respect to Pd's needs.  So this chooses
 # from a list of fonts that are known to work well with Pd.
 proc find_default_font {} {
-    set testfonts {Inconsolata "Courier New" "Liberation Mono" FreeMono \
-                       "DejaVu Sans Mono" "Bitstream Vera Sans Mono"}
+    set testfonts {"DejaVu Sans Mono" "Bitstream Vera Sans Mono" \
+        "Inconsolata" "Courier 10 Pitch" "Andale Mono" "Droid Sans Mono"}
     foreach family $testfonts {
         if {[lsearch -exact -nocase [font families] $family] > -1} {
             set ::font_family $family
             break
         }
     }
-    puts "DEFAULT FONT: $::font_family"
+    ::pdwindow::verbose 0 "Default font: $::font_family\n"
 }
 
 proc set_base_font {family weight} {
     if {[lsearch -exact [font families] $family] > -1} {
         set ::font_family $family
     } else {
-        pdtk_post [format \
-                       [_ "WARNING: Font family '%s' not found, using default (%s)"] \
-                       $family $::font_family]
+        ::pdwindow::post [format \
+            [_ "WARNING: Font family '%s' not found, using default (%s)\n"] \
+                $family $::font_family]
     }
     if {[lsearch -exact {bold normal} $weight] > -1} {
         set ::font_weight $weight
         set using_defaults 0
     } else {
-        pdtk_post [format \
-                       [_ "WARNING: Font weight '%s' not found, using default (%s)"] \
-                       $weight $::font_weight]
+        ::pdwindow::post [format \
+            [_ "WARNING: Font weight '%s' not found, using default (%s)\n"] \
+                $weight $::font_weight]
     }
 }
 
@@ -297,17 +470,22 @@ proc fit_font_into_metrics {} {
             -size [expr {-$height}]
         set height2 $height
         set giveup 0
-        while {[font measure $myfont M] > $width} {
+        while {[font measure $myfont M] > $width || \
+            [font metrics $myfont -linespace] > $height} {
             incr height2 -1
             font configure $myfont -size [expr {-$height2}]
             if {$height2 * 2 <= $height} {
                 set giveup 1
+                set ::font_measured_metrics $::font_fixed_metrics
                 break
             }
         }
+        set ::font_measured_metrics \
+            "$::font_measured_metrics  $size\
+                [font measure $myfont M] [font metrics $myfont -linespace]"
         if {$giveup} {
-            pdtk_post [format \
-               [_ "ERROR: %s failed to find font size (%s) that fits into %sx%s!"]\
+            ::pdwindow::post [format \
+    [_ "WARNING: %s failed to find font size (%s) that fits into %sx%s!\n"]\
                [lindex [info level 0] 0] $size $width $height]
             continue
         }
@@ -318,46 +496,83 @@ proc fit_font_into_metrics {} {
 # ------------------------------------------------------------------------------
 # procs called directly by pd
 
-# this is only called when 'pd' starts 'pd-gui', not the other way around
-proc pdtk_pd_startup {versionstring audio_apis midi_apis sys_font sys_fontweight} {
-#    pdtk_post "-------------- pdtk_pd_startup ----------------"
-#    pdtk_post "version: $versionstring"
-#    pdtk_post "audio_apis: $audio_apis"
-#    pdtk_post "midi_apis: $midi_apis"
-#    pdtk_post "sys_font: $sys_font"
-#    pdtk_post "sys_fontweight: $sys_fontweight"
+proc pdtk_pd_startup {major minor bugfix test
+                      audio_apis midi_apis sys_font sys_fontweight} {
+    set ::PD_MAJOR_VERSION $major
+    set ::PD_MINOR_VERSION $minor
+    set ::PD_BUGFIX_VERSION $bugfix
+    set ::PD_TEST_VERSION $test
     set oldtclversion 0
-    pdsend "pd init [enquote_path [pwd]] $oldtclversion $::font_fixed_metrics"
-    set_pd_version $versionstring
-    set ::audioapi_list $audio_apis
-    set ::midiapi_list $midi_apis
+    set ::audio_apilist $audio_apis
+    set ::midi_apilist $midi_apis
     if {$::tcl_version >= 8.5} {find_default_font}
     set_base_font $sys_font $sys_fontweight
     fit_font_into_metrics
-    # TODO what else is needed from the original?
-    set ::wait4pd "started"
+    ::pd_guiprefs::init
+    pdsend "pd init [enquote_path [pwd]] $oldtclversion $::font_measured_metrics"
+    ::pd_bindings::class_bindings
+    ::pd_bindings::global_bindings
+    ::pd_menus::create_menubar
+    ::pdtk_canvas::create_popup
+    ::pdwindow::create_window
+    ::pd_menus::configure_for_pdwindow
+    load_startup_plugins
+    open_filestoopen
+    set ::done_init 1
 }
 
 ##### routine to ask user if OK and, if so, send a message on to Pd ######
-# TODO add 'mytoplevel' once merged to 0.43, with -parent 
-proc pdtk_check {message reply_to_pd default} {
-    # TODO this should use -parent and -title, but the hard part is figuring
-    # out how to get the values for those without changing g_editor.c
-    set answer [tk_messageBox -type yesno -icon question -default $default \
-                    -message [_ $message]]
+proc pdtk_check {mytoplevel message reply_to_pd default} {
+    wm deiconify $mytoplevel
+    raise $mytoplevel
+    if {$::windowingsystem eq "win32"} {
+        set answer [tk_messageBox -message [_ $message] -type yesno -default $default \
+                        -icon question -title [wm title $mytoplevel]]
+    } else {
+        set answer [tk_messageBox -message [_ $message] -type yesno \
+                        -default $default -parent $mytoplevel -icon question]
+    }
     if {$answer eq "yes"} {
         pdsend $reply_to_pd
     }
 }
 
-proc pdtk_fixwindowmenu {} {
-    # TODO canvas_updatewindowlist() sets up the menu_windowlist with all of
-    # the parent CanvasWindows, we should then use [wm stackorder .] to get
-    # the rest of the CanvasWindows to make sure that all CanvasWindows are in
-    # the menu.  This would probably be better handled on the C side of
-    # things, since then, the menu_windowlist could be built with the proper
-    # parent/child relationships.
-    # pdtk_post "Running pdtk_fixwindowmenu"
+# ------------------------------------------------------------------------------
+# parse command line args when Wish/pd-gui.tcl is started first
+ 
+proc parse_args {argc argv} {
+    opt_parser::init {
+        {-stderr    set {::stderr}}
+        {-open      lappend {- ::filestoopen_list}}
+    }
+    set unflagged_files [opt_parser::get_options $argv]
+    # if we have a single arg that is not a file, its a port or host:port combo
+    if {$argc == 1 && ! [file exists $argv]} {
+        if { [string is int $argv] && $argv > 0} {
+            # 'pd-gui' got the port number from 'pd'
+            set ::host "localhost"
+            set ::port $argv 
+        } else {
+            set hostport [split $argv ":"]
+            set ::port [lindex $hostport 1]
+            if { [string is int $::port] && $::port > 0} {
+                set ::host [lindex $hostport 0]
+            } else {
+                set ::port 0
+            }
+
+        }
+    } elseif {$unflagged_files ne ""} {
+        foreach filename $unflagged_files {
+            lappend ::filestoopen_list $filename
+        }
+    }
+}
+
+proc open_filestoopen {} {
+    foreach filename $::filestoopen_list {
+        open_file $filename
+    }
 }
 
 # ------------------------------------------------------------------------------
@@ -374,83 +589,115 @@ proc singleton {key} {
 }
 
 proc singleton_request {offset maxbytes} {
-    wm deiconify .pdwindow
-    raise .pdwindow
+## the next 2 lines raise the focus to the given window (and change desktop)
+#    wm deiconify .pdwindow
+#    raise .pdwindow
     return [tk appname]
 }
 
 proc first_lost {} {
-    receive_args [selection get -selection PUREDATA]
-    selection own -command first_lost -selection PUREDATA .
+    receive_args [selection get -selection ${::pdgui::scriptname} ]
+    selection own -command first_lost -selection ${::pdgui::scriptname} .
  }
 
-# all other instances
-proc send_args {offset maxChars} {
-    return [string range $::argv $offset [expr {$offset+$maxChars}]]
-}
-
 proc others_lost {} {
     set ::singleton_state "exit"
     destroy .
     exit
 }
 
+# all other instances
+proc send_args {offset maxChars} {
+    set sendargs {}
+    foreach filename $::filestoopen_list {
+        lappend sendargs [file normalize $filename]
+    }
+    return [string range $sendargs $offset [expr {$offset+$maxChars}]]
+}
 
-# ------------------------------------------------------------------------------
-# various startup related procs
+# this command will open files received from a 2nd instance of Pd
+proc receive_args {filelist} {
+    raise .
+    wm deiconify .pdwindow
+    raise .pdwindow
+    foreach filename $filelist {
+        open_file $filename
+    }
+}
 
-proc check_for_running_instances {argc argv} {
-    # pdtk_post "check_for_running_instances $argc $argv"
+proc dde_open_handler {cmd} {
+    open_file [file normalize $cmd]
+}
+
+proc check_for_running_instances { } {
     switch -- $::windowingsystem {
         "aqua" {
             # handled by ::tk::mac::OpenDocument in apple_events.tcl
         } "x11" {
             # http://wiki.tcl.tk/1558
-            if {![singleton PUREDATA_MANAGER]} {
-                # other instances called by wish/pd-gui (exempt 'pd' by 5400 arg)
-                if {$argc == 1 && [string is int $argv] && $argv >= 5400} {return}
-                selection handle -selection PUREDATA . "send_args"
-                selection own -command others_lost -selection PUREDATA .
+            # TODO replace PUREDATA name with path so this code is a singleton
+            # based on install location rather than this hard-coded name
+            if {![singleton ${::pdgui::scriptname}_MANAGER ]} {
+                # if pd-gui gets called from pd ('pd-gui 5400') or is told otherwise
+                # to connect to a running instance of Pd (by providing [<host>:]<port>)
+                # then we don't want to connect to a running instance
+                if { $::port > 0 && $::host ne "" } { return }
+                selection handle -selection ${::pdgui::scriptname} . "send_args"
+                selection own -command others_lost -selection ${::pdgui::scriptname} .
                 after 5000 set ::singleton_state "timeout"
                 vwait ::singleton_state
                 exit
             } else {
                 # first instance
-                selection own -command first_lost -selection PUREDATA .
+                selection own -command first_lost -selection ${::pdgui::scriptname} .
             }
         } "win32" {
-            ## http://wiki.tcl.tk/1558
-            # TODO on Win: http://tcl.tk/man/tcl8.4/TclCmd/dde.htm
+            ## http://wiki.tcl.tk/8940
+            package require dde ;# 1.4 or later needed for full unicode support
+            set topic "Pure_Data_DDE_Open"
+            # if no DDE service is running, start one and claim the name
+            if { [dde services TclEval $topic] == {} } {
+                dde servername -handler dde_open_handler $topic
+            }
         }
     }
 }
 
-# this command will open files received from a 2nd instance of Pd
-proc receive_args args {
-    # pdtk_post "receive_files $args"
-    raise .
-    foreach filename $args {
-        open_file $filename
+
+# ------------------------------------------------------------------------------
+# load plugins on startup
+
+proc load_plugin_script {filename} {
+    global errorInfo
+
+    set basename [file tail $filename]
+    if {[lsearch $::loaded_plugins $basename] > -1} {
+        ::pdwindow::post [_ "'$basename' already loaded, ignoring: '$filename'\n"]
+        return
+    }
+
+    ::pdwindow::debug [_ "Loading plugin: $filename\n"]
+    set tclfile [open $filename]
+    set tclcode [read $tclfile]
+    close $tclfile
+    if {[catch {uplevel #0 $tclcode} errorname]} {
+        ::pdwindow::error "-----------\n"
+        ::pdwindow::error [_ "UNHANDLED ERROR: $errorInfo\n"]
+        ::pdwindow::error [_ "FAILED TO LOAD $filename\n"]
+        ::pdwindow::error "-----------\n"
+    } else {
+        lappend ::loaded_plugins $basename
     }
 }
 
-proc load_startup {} {
-    global errorInfo
-# TODO search all paths for startup.tcl
-    set startupdir [file normalize "$::sys_libdir/startup"]
-    # pdtk_post "load_startup $startupdir"
-    puts stderr "load_startup $startupdir"
-    if { ! [file isdirectory $startupdir]} { return }
-    foreach filename [glob -directory $startupdir -nocomplain -types {f} -- *.tcl] {
-        puts "Loading $filename"
-        set tclfile [open $filename]
-        set tclcode [read $tclfile]
-        close $tclfile
-        if {[catch {uplevel #0 $tclcode} errorname]} {
-            puts stderr "------------------------------------------------------"
-            puts stderr "UNHANDLED ERROR: $errorInfo"
-            puts stderr "FAILED TO LOAD $filename"
-            puts stderr "------------------------------------------------------"
+proc load_startup_plugins {} {
+    foreach pathdir [concat $::sys_searchpath $::sys_staticpath] {
+        set dir [file normalize $pathdir]
+        if { ! [file isdirectory $dir]} {continue}
+        foreach filename [glob -directory $dir -nocomplain -types {f} -- \
+                              *-plugin/*-plugin.tcl *-plugin.tcl] {
+            set ::current_plugin_loadpath [file dirname $filename]
+            load_plugin_script $filename
         }
     }
 }
@@ -462,42 +709,27 @@ proc main {argc argv} {
     set ::windowingsystem [tk windowingsystem]
     tk appname pd-gui
     load_locale
-    check_for_running_instances $argc $argv
+    parse_args $argc $argv
+    check_for_running_instances
     set_pd_paths
     init_for_platform
-    # post_tclinfo
 
-    # set a timeout for how long 'pd-gui' should wait for 'pd' to start
-    after 20000 set ::wait4pd "timeout"        
-    # TODO check args for -stderr and set pdtk_post accordingly
-    if {$argc == 1 && [string is int $argv] && $argv >= 5400} {
+    # ::host and ::port are parsed from argv by parse_args
+    if { $::port > 0 && $::host ne "" } {
         # 'pd' started first and launched us, so get the port to connect to
-        ::pd_connect::to_pd [lindex $argv 0]
+        ::pd_connect::to_pd $::port $::host
     } else {
         # the GUI is starting first, so create socket and exec 'pd'
-        set portnumber [::pd_connect::create_socket]
+        set ::port [::pd_connect::create_socket]
         set pd_exec [file join [file dirname [info script]] ../bin/pd]
-        exec -- $pd_exec -guiport $portnumber &
-    }
-    # wait for 'pd' to call pdtk_pd_startup, or exit on timeout
-    vwait ::wait4pd
-    if {$::wait4pd eq "timeout"} {
-        puts stderr [_ "ERROR: 'pd' never showed up, 'pd-gui' quitting!"]
-        exit 2
+        exec -- $pd_exec -guiport $::port &
+        if {$::windowingsystem eq "aqua"} {
+            # on Aqua, if 'pd-gui' first, then initial dir is home
+            set ::filenewdir $::env(HOME)
+            set ::fileopendir $::env(HOME)
+        }
     }
-    ::pd_bindings::class_bindings
-    ::pd_menus::create_menubar
-    ::pdtk_canvas::create_popup
-    ::pdwindow::create_window
-    ::pd_menus::configure_for_pdwindow
-    load_startup
-    # pdtk_post "------------------ done with main ----------------------"
+    ::pdwindow::verbose 0 "------------------ done with main ----------------------\n"
 }
 
 main $::argc $::argv
-
-
-
-
-
-
diff --git a/pd/tcl/pd.ico b/pd/tcl/pd.ico
new file mode 100755
index 0000000000000000000000000000000000000000..2da5c243623c9ea56b6faca91b5e687d2c4f62fb
GIT binary patch
literal 25214
zcmeHP30#)N);}r&DVu1x3xW%3E+B{?>&q$#xbK-;3TA>JzDTC1DWn-Uw@NKjGtE-l
z)Lbg?ws+g>wwLX7OS81Z>bvv(|MN1hF9?Z#cJurF?$n>>eV%8YnKLtIX3jZtjv@`@
z3hC3w3s)z}au!)9B7T0h`)?YG{2R}_ylnS@4Me`WLXcmO-&J;ah<G$*{;GRdk$tV1
z-*%tXSY(2~$?qpSI*UBp)#S%L^36&&`TZozNyKM@NFZoH6*3VZT{?jdlkk}kwp1*F
z|E0LtcEMl#1d64+yc{o-{$G^hF984X5&v9pvYwlAQHYCmiheO2f5YZlN8wUn<@1mb
zAFmc7EkJE-Tm$rzS0fjEz;B%Mt=2qb%(qT2uv({E&2v|!D)E1%D#QO}Wu?W)Tx@j-
z>5s>FsvlQYR+im_=lNx52A}Et$c-2Ics>V@nGpr(cX(XMa`IIvp72#^StZ}0{1TRb
z8noiB5*Kk{-DPFDEJr_Asu=V#17BEKPGznO>QaMU%5tU9flgHn^3k)NWv7MI;FCox
zN9DR$3st3fR-TqpJT&wxyp$9?SICd2c(oXhiuLnG3bdT4%CF1GW%<Q|O3}NTn##&$
z$Zu7-p6ZmKQY6$%`gOmlT%EtHB;KmasiMbP!i<g36=<?8yUw4h3vgvmROPR%WdB%8
zF;G^vteEw(eBGbLe1t+&<u6{S7o{m(j#yn>GWkNcsj~94PiR+;uV6?D^(VH^PM4OJ
zq9v%nYIP~GmJ}8O))H%eveoK?$1YY01)Ee$ECw%-_@jDqv4+(8!o?bI?OMQ*tDY83
z78%@7Q&#OoZdh=`hI!<G>M1WT)w<m!ua@UUMb<6n<}dDEgclvp$)DSG$MzlD{my+}
zw0-;btrO3E-WAVl%Rhr3^f@PgUK`X~WI5+~!->c~xli>*;jZCU|NFLf-G0t<@%Iyr
z8gF#>*ERq(@}1|_&IwgNPB6c(Qb&39th%}=RnGg%_<FR8K7BQGn=9H=*~r$wqg-{7
zj><xPEx*Lqv*qjJQBU=&?e6Sf?lXQsEMWGT;)b}XN?y1Uwp5ID3BRzf=nMYh=f&+a
zRs2(^CpOB&HE-1H={}*;eF~=A=5+xU46OS3@wqLLfr~F!6MzdwA(kNf!jAieXj7Sf
zqHOP7T+37mRw3jsV}5?0i=~|l67O@7TU7Bvl*95C=H?cbN!bZcu2=d!p5eVGE7b4H
ziWlO0yjS|Z4;I|hk_$SR-=z=~VqH7oiG4`l&&kDVe;Ug@h+=uA+=otE7h>bl^p{{U
zWnGo0(R8#fISf~wuuvm~<?^pRS+O2EJ{j5|Hs0ahHK411m_0L!%bQ`{?)bjR@}fM)
z_f3k5xXqa4ihIZR(h&C!@1@K3?f#DU0semd`k1eACFKscHGh)hj$%faW6!w|6eB<3
z@QurlD68DphGJZ6!o1Vh7-U8wpL+p*fZ_9U{5Fa22wN%!Q{xwyTVL=OKQD;Soxrxq
zVez?*tQ1Ttb6pEn2?UQ^*JR}i=_1!v3O%i0NY_Fu<p?lVVWkz%T}oADX=N#71#3x3
ziM80p#Wt!9^K8BAvK_)BQo|zKeM5iSy?%&4Y-@3Y^J59})dat-E_b@>VvC!Y(s6B2
z1zdXe>?tq5{IV1j6i8N9mN+>%N&oN&*|KGe{PUmxl=+KplBrXt3feELR;`kT4I4^E
zMus#C_m;5}Cd!Oixw3EHK6&GfH)O|-9dh4&_sNGJekga|d8fSl?z=K=+BEs~*I#8;
zUV&U`X)Q@dk~AR&^X5zH6Y0`AzP0oV>o3lIuaK1T6nW&4N5sR!L)rwlmz_Iz%DwmA
zE9=&+lUr7;k%0pTO47hoS-g0$ELwK6{B7MGGU(u7>ApNbx@C2jsoB@b_=%IHUAuM?
z^;WR-tLP!i|F&A(Vm-vKV@H`Zb(%D4)=0W^=^`UXj+E!0e_qCqA1|@5^p}KZV+B%-
zEMLA{9(w2@adUH%*Is)~9(?dY*}Z$Wj2$~x%i-SM-ZEkGRGBz&qD-DVS?;;#9(n7n
zw<PL~V2OS$L=GG{AoJ(Xm!za52@VOBCaE4WGdE9MU0r2y>2g`Re5K?<!f@;0E;rwL
zn^<0ql=$bPW#Ch((xI=v<j$EZasP;rcHw@qX8m2#x?elFp?I+b^$n4uM~}*<pMEOu
zzyH2`@WBW2@y8#_C!c%*J*JQJ3GOSw!NJnGb7$$)xr>Y#F+%q4-77D>^pae2%{B7b
zXP?REpMNfKFNaIgGx3u6tVKFR`^&03HpuEbH_9)+{354MpO&!x{iR?3a5-_}g#7s9
zk1~4nXbB7ql)!G?rCawN(xXQYiGML#x-IP_Pe1*%H0$Oq&3pSuSoi=**_S4%4`oXD
zfJljmjMDm6;=QR7yE9ErojN5y|NOHI9z0lv4I3stTm7W<&Q9|E_utF0W5=X@hYsTF
z>nmN>^^tDtf~EV$5IKJQxcu<L53=y45^3AEtvvbUlk%^B{Y$?4?mPM7i!bEMFTa$C
z=le*^t6>tlGghJ=Op*RCT_yA8&67==Hpy3CeI;Li{k44a%{TJxx8F+I;~6sOP^MTc
z7Kw{bl%Ib3Nlu<TDYve<UHbLwCtfYR#mC1-BKL<$_+gZLI8uK9{dZ}b-bR}C^AwL5
zcS(9WLB@=qAZ2UskPbaNN>lKO<)s0V@Is6%yX98NnmJ2ya&jbT1=_h`g7_{C6rT-#
zGIYc!NjZ`%1Ge`Um$uFlnAcl^Z;TMj3xlP3SW9Ua(?Ozcjgw{x9+H?mP*Qgfk?y6z
zGT_cg88&jXj2d^fc(-pM`PL%w{i>HVa%&``HcpnwkLO6nkYUnxl8^XJX(t_0{G|EP
zwi39?S6XasFMh$Dq<2uTM7^0NaSPI<gRh@N<|jze!bN%>{@H(Cee3Faq#lNh-pxXx
zrJpy2%&7Vx@~d^r&YxC4l`&C&kkzH?I}faLPEM_DsfjItH3tvQ%q)tFOB*$Myw13|
zD!;pTMR{py$^G~5*s*2v=H0t?9XfdMz~hfUv}gYHvwd2*Ht%*_RsMd~n{U23CMK<U
zh52C1md(3&?>eMEcnBZN^qJ*4)SiE7c~hjDZrV^+{yo>{=Cm4mr73@DDKanFxDoj)
zuw31L|NU5~u|Bba0|y>Q1vii&hy10k&6_uOEin~5RM_2R=&qF5h7VQc*95gVTmFQ`
z?(Xj6+?XFt9?_tKNczZU+{Y!N#kv9#<d7eB5)t<VTmC_JAosoQeA3J02SKwO@^5J*
zG9-4#h7Bmc;z^Mh*ZKRCMY_+|`Prp2b7uvfrKhPIHcDHI%jY+i<m<7^42CG?I0QKZ
z?fJ`@|L(y~y*h8MC~uCfuHyb3TUv^ESM?Ao06`A<+aNzv<2fl6KJM}W$GBIu{2-`C
z{(=VgqFbCdS44}nEWJMm7w@w?feKQq=vlP1oaHxc({w}^asOMzkjB#3!>J#t^L7}r
z`7=%aKK{1+H!WH6cahYJl9A&`8@Lep?PEOu`lRc6^SQiQmA_3>?=I3|5P0rErzb}x
zbg!O&&kafWJ9YjZ{^c$MM3y%o33GOF3`)yOOCT_K+qfD-%pvHU5Azrj^53n0uzwor
zF)Q71<vxflRrzhRU{B{?P5z2`jV3^DXp}f&$3|QJ9Pq^1{P+M&+WA-81n%Qp@xlu)
zv}x1E&ei3mhhmmTN3V?KG&^+YkZqU?N88#fTRrZ&%L6!QW&wC~2N?5!ZJO;$9Ca0w
zvvVIC+0Hkx-*#DZ9V*cCxGH~>&a+yzYUSFZXH_e2*$2U3<jB01*z*EgAYF;n4atRR
zh|8$VMoe)(RpmdH5$b$9maEXvjL49;$F{w;T58*N?AT^HRb|H|>lLpfuVTl}I{WAk
zel70p+p{P3zP8u;&Itd5eCE~oW?ugOAAEpa^1Ddisy;C9YPn|luNaV?j=itn*8iEi
z?CM%_RDTry*<<UEuDa&7Yr6ja?uplIV_rQ+^}FzPpMKKu+ck&Rev$p}AJ5b$|2HqT
z?=;2liJ-4Pe`jhv@_TwdzwqS`JNfwq-I-mV{6CzUG&%ldRuS~`)AcAHdo(^+zz_Pq
zUa$PaEz>4VUWh8{lmAH6$uNHKCaUQ8eHD*a@4{+#SrHNahYf$k^2V+4AGMo4vD&*@
zZ^qp7<=(JisNz}AN2k}Cqx!=!$KU_Q>HZHtSLV6>x8mxrYP}1n-1JL8)W=aTy|?YY
z>Pk$l+2vZu(I#Dt;kx!xFY+mMCC7Av<@o;BZgsKtteSSJn)i-<&EME|9PC!EdK8PD
z^u_kaK~HsR<|KtT7HhpF`t=aGs#kAGhAx?YaEPQI7^?Ndm8;fDYHF&arlm=qb)J@$
z<6nq|eifs2ytr4wrJ!iO%)Q}8t;?=ny;@Q#Qe<HHKrOdtU!NmaTycd!929Iz^5Tmx
zYJH$(%a%gD4}z3neb+iIm{`V*8>e;D2OfAp`UC|@5cDtV$Jih={S6v4(7Ni9B}-(^
zoH;TFIyUue>chYN_M6s0M~xa~*XO7&zxUpITJL`6op-bz?CI$#ci(-t)@L?u+$eY5
zb(ahpG)U6Z(*+xy*54;gn4on$?4w#wvb+SH^2I2vFFp0tQ(7Nx-@d)pSEo#wBDdUf
zi>zF^QeJuG6?yg5SGB%+<Bd0pv$L~cmy>`Iou%XOE?WOw3mtUs+_^FrmLXb)(x9`a
zJ~>Fz4rfT?#*Jmcf(3S6k#>aegeVD5j+Vsd;<TP=bX3{|KKke*x%Jjt1tPMnSg}Ie
z6waJEBWpL@EwRu`srMEZ76S9M4mWAiB)eW}^w*<RI&frUq(nzYOL%y=3>Yv#cJ10F
zd-m)R*vlmi{WEY-hNNX=La+3d4$v*1c;X4W4jTPNUu_@A$C%{efGaOAPxA8fB_A00
zRGL5{&~|~TSu<q%b=OPKq96%b)K3Nv86rc54wa!phuQVk^z;l#hdm@CGgI3P+}%Co
zig0)F4ENM_0jss}9G%tZwMPHkuwjGDo;_QJ4<8P_eWYDqEkM5;9TtWp=@8!$LY|-W
z4GEEueqqo>6U8zb{CjnV^h+2Z;e(<j0=l>V)Ijmi>8AD6h#ZT=6eVlBM8~WBrOW7U
zl9ipUbyT;Wo`Qn}iG}{0mNrP*kMxt^$GS?d2f_s+kM!u-OWRMVA5-6LX7P}gncm_)
z+FKrd^ier-<cK6DCW_lMZ}GnNO7ULMM*RK#C3bhLv_v0vDF~2|!u}Ep`_65*ua};i
zI!VuU(A75u$*@tQW%%eZGIrZEX|)f}d-st{*anuDt&*Jlx#GK^tHkU|(>5lD6``)5
zKY}l6OP|_4Xz>bc54ELV?$(=nSv2NwH`toCB0;0V4E+^~z%6b~yK6goTNX2V|Gw<3
zy^r0u?cs->I9&eJqmSH_7qR;QjzvY@g20~paLxCNbNWZx-rq5IZt=oZ`}b#O!zS~X
zDPZxE@TfcV_jkO$taRy!RVd)G>ECS}_;yK!u3%B#fc5(Q`Y-+Eqdt6phos%*C*#KO
z{iBO>!h>w@twSZ86+GN-+V00X^!R9-jlw0f``X`^ew}jj{u~*S{n%cA^L|mzMBDq2
zp_1x{1JS)5uWG+HvzP7tQ;#mrJ^Oz4T#F>FDv+43cgjcinJ$<N9ZO_==unwG_i_1X
z^}hIJ$#47F-!HlWXX{dU=grYF*cU1Xn~?qR-#&WG^x=)S<Nbs?ze;G@G;!GS)qKBT
ztekzn;&+z6VARNwGafEpsNcU65YYQRd_OxgGvl^SdVCHB26pv{Lm%r5=wc4imBVA>
z;}1^Y_b+t`>-_5$ta0<_@7XhH(vwfZRJQKD?O})dJ<Zohx57qNclz!26Pq@*ZsYz*
z9a&aBa}0~&nG<!0G}rv&-&g;A{E=5rUswD06JCD*`wiE+{_xt&I^O^DR3qnBQ(WGB
zp^o<rywk6Lw&m2#AJ_i=ng&kI26@eLzNc~R@8!Xt*1TKf+31ZI>v;dKZ$DV;H1GJy
z=jwR>P30G#eZK2|K7788_f24?WUAx!c^*?%IC|NfJJ{ruSjr|QImjtDuPl@0K*78s
znOj&S@$vDJkdPoSQVG{^sI+qU@L|0V;!sQ5o4FTq-=|#{79A~982jtc;Ui=i_5@@1
zH2Wa!?Qg#MrhQMN4LArg$h9-ClW8+%%H3P;mAkiYlTE;!xrH(Z`%r<#Jjob5L^835
zaqsLOF+khcx4~|o%DoJ7MLzP=F3Wvt_M8ITTlL<}0W@}3vv(QW>RqUBD{S?~4sG@=
z?jw{_Xj`T|9gDQwcH3>TcI{gD+xSVRPMsupNr?1Y5+UpF*(!rU*N|buu@{bz_3PJb
zxrcju;<Is()dp(WYwENty=TS4=Gtv}Ckb5HOTyNNN#vRsZCj`8v|`0A`dp&JEbK|w
z`AT3xZ&`)&g0*+9*P}-JKIN6{**TJpL$VcRx5<jzRtx1M@8Z@HP})H(i&JI7q$v^|
zXORWBEHk71-|K&Spng3tH?F4->_<b9hIIEA88#_(K=Y8kEu#`522O?vuJ`bfkuBP~
zH0zZR(b@|}zS!9#+BYB6^r|sY@!?_6j;8u0HEwrZ{3Q2id;rC0s$a_>H&@?ukLZN>
zHr+(h5-rZ217<pVw8RHczo#_0BDq6kfA6-fQoKio;>2}qaKr4*ojV1@WHrcv(X^mv
zpB}jmq)*StoHNVee%h$f6FWNGUpsF6HP<`b=jC5L0k*%o64&WVcW&vYt10eWS8e(^
zcYkhbE&wID%MGN9^l2#tSI0yp5nhKv4=G=u6TsG9E+9RAKr4V!9NS?McRbSG4gjM`
z3NQ>9aT#GaXh{Pi0Jeo~Fyp|!r{qc#=~$o;C<RtrMpzD7ZUiO+IAM@BfHS~E`Z+Fq
zeiN`A*asZEjPN*Uc@S6%WCH$xO?VP<!fz^77T|k;f^)6N%*<5T+1U!d3Dq!t`gG+8
z_!%ez*l3gid>51>VE<5#z!|=Rok8I|U*Viw;jmJ{CqTirqi}|=V9%)lZKD7z(0YBr
zMbl5-j|YwcwbG7n6?`KU&X^VCC57{J4ajy1=h_;Ou@r$ba0Qu51LxZc@}36HtrcV}
z4V+~w$W<CR`&N*}6wdS&d;%0?GX)!qf(=RG{6#@FRNJ;~Q&3Bu27RLeY!K(tkF$$o
z@D)%v=eGkkDMi4>r66Z&`p?$}(r?;8`b`^1e{F3b{f=#bT&Q3lQjmca<TnMMih9ux
z|CM9xTiB5_;HN@g9R+z)1AYg8I{h36jsa|43Vs=S4BmeGZ3SPL^Y%aKCto<y3||Zd
zV}*tz{Tu_sL%bRu;??v&ucrTN8w2u?;UmW}fL>weA?PBShhQ73H~pl?@Ev)Ne8YS4
zx?>w^n+K!=b`b?X9|hx!9((dX^aTYU9R)dE^9tudUG#Gv)Z`(Y6xW-6*x#5}!FFp0
ze0CIcm2>EKYy*xS6~<7()@xq_U@KPe0aEZK;rgLi4s^U~^SHJ#aO5G50d&E7)6ccA
z<~qc>;UrKe{qP}BnE&?gVBc2o!O`t^EQ{s9=jy!lJMs|c@rBS&zJ;CHP7D016m0N%
zZ6yB~9%A1UoC_9<#h#z*71s^&0epWHoEYryplj;qj_auDf3BxB*CEbh(hr+pz5Ac*
z56%<pb&_Z6n#ZJ{bi&@Q^FtTa>xbD3c;y~t#(;B!^P#qNv?l##FW@=^-?e(tPkL(7
z4<9W}!;Bd-6wWUc?7ez#GW!7Mx1o>x&*xl+%w7inKl^*`frft52FH2q*apKx+zT#*
zei&@4^}nIbT;c7bVDwY3zy7+WmF1JK`5wpX_O;ZE!J<WrE{cA$4v~jox2Sjj*Q6hQ
zd<u3GJs%y{7T9d;<IuHhS38Y3L(qEz$ADuGA4mH;)Bk1+9Opqzd4hYH83Xtg{Sp1`
z(RT@E|E)<s&Xp9OabG)l@SwsumZpb&4!=P?2Aty@7mfkvf|2#$ziFqP?@0fJ>}6&Q
za2`|_{nTYT07;<#IQhRO{hYs)AvlK#xJcM(V!x2zxwpZltZ-&%e@A)=_^{faInr;&
zptgBn_G8k|c?>^P(*F#~qmBZDfHVQ9dr=SY15ynAI8Us`|2W&T_dn}5{ZC#(Jej7S
z?dHA>ze+7*b3IK?PF6UdQ@7uK`$f}lc<AAWA13|Jg3j?k55Nte4n{qI`ar6oALlL#
z_CR}`aKBN#d-ql~d4Y47eOPlpU^`5EI4<xVwwJYk|Nc7V>blk;jsbj;4gC{9D}98F
z4oN#h05HhV&-D`bb;7JaoENq2XQZEN4`nX+uPVyqro2C*-^fI@&EqQipF=qlfnI<o
zK;4miM4edEPhN(fu)-O&f^V{d|Ee80uhy~)<s8ySxwJ0&x$Zd16DVJ|;e7Nvw!zE+
z@(}kN@DJ&q1W;G^0;oHZe)3OJuU@^5k$(8;+QGR%nDxi#0)Mn0v;R3K;ryk@dt6%y
z_&aMEw5D#xyar}Y*IZY*Z)08iJLnt(kbg)&*B<ge_1`ee%kN-wS8zSm;2KDndzM*W
z`V9}6{xrO8bOW}*=mw;jfWN!ebKy6k2>96B!Mz6S^#RbC43J;k0ro%FpJsqR_NcG9
zUzqjT^aZcvVeW(V(QVA!WgBYh$A3gW^?dR<<u%I8)PD)ef|Nhe|2sh^dA}vVc}xB$
z_}dF-{a<pvaUHJ-oT(_vF_Z(jo*1xPg8Bu)IZIIABdB*1I8WB#+GarghS<D$v%b>C
zK>Gt_UjxoLf_4b#BX@yL+6ZZHBkyqiB>m(e&SxGPE(LA|_5*tXW5-%v^}H0<Wxx_(
zAy5Pq0JDMIiv&ZB@_{)3%bHsST8IgAPa73!CGDh}`xmd=3t9u*7s3Gxuo5861A!EP
ze3t|y0&zeDz_H}sP5vVvvb>8)h7QBeE+~W`tptCweA3N4>~~WapV5BU6^IA8KW78?
z0rvvz8-o37`uIBho&;n7Apqqkj;SdVKQAird&3j#*P3vo+f=}|wE;o_+J-n*?Ds=e
zScfaI)&XVsy%@*>I3{d6`H$uQrxWKcdB6gY=6?Wh17BC+16&>PKJMQIUIz{WBLMP|
zH^Bb-Pp2lR&<{vQ?1w_!ib9NxLOh5<ERAyWbytY1QSR;CRqLR33UM0>u_Oxd9;!uv
z&k1~Y4G;v72mdPiJEHE4E8|)##IPvDv#3^<)(Y_|3UNRRu{H{^KngJ}s&!a9g_skC
zSQX{dtu^VN2m}N4KH=Q@Pbc=jroTmeD}^{4g%}@|RGz5d52z4}r4XB@5G$nMTc{9=
zqY$5?5YwZs?B43KqTegLxoQ^CTp?CTA*M?qK1m_YNWrI3!B0=YPfw*E9IOzBr4UP|
zT6AxDS<#O;CWW{qWqBz|!B0~qJR7Imko@-~mBKbW1ztMLu^6ln+oT%1IbT-vgO5~?
z6`d5~>d=Nr1%FeO@EqFkOuW)<sN$!zC(~8;>>iYdF9-U$ev<#wNq@KH{wn6RP=z>1
z1s_ahdC@i&hM$tjPZg;uFsqv*{ak-IfB%a0$E^PY8+kh^#J;KMxBA-J@G9EyGTH!s
zLQI~5AF6FEjwGoziEWg#x9bm}c^vRpuK#BLwKR?MRPM1JDzu`9ih46h!M9l<o)Kd~
zej1<<YYBdeRfro@EiG-7SML_bLHl4J5by&2iv8EfKcP)iJr&|O72*~ZVlP$XTR|%7
zjp}28cuLhAYZPKMm1l47??H16!2SDD$Ui9Nf)o3Ub$4l!=AjTzsSu;75Z|c~H>tvo
zT%{0Kst^OJ5a+7A!ka0?qhfs0lt+;FxA^WVpcz2<@UKeTH`_ReUZD`@sSr=A5cjEE
zf}1F(W{nhLOqG+DlS168LJX=xT&+S}t!fhBMj44`my~h;3f@7r_Qd|n0SYbvQ;_#^
zpdDoVH~keoP|iwS;A_My{!JEM1pi*bWpTy7-k|BYBv()EPX#qg9YuS2W%~23zPkKk
z20<Z}ON_qO{KJjM4~PXC=wBkm+=*&Wln=OHdjQlI|17Y4+7!5^Gys^mzEMV@tU?>Y
zz&{ZwC_4%W0N7q8(!#wW3!rUd&7X+XD0?9=2=D>e2V5t^fcJo70A&XaoDUs`z0VHV
z{f;{#B_-v!0nP-DI|4SV;{;+qj>EtCxW!^Q9v2sPJRl(88<am4plyTovma>NISFuH
z*unF0+HH;9&)EFT`Ju5@8hfI#Y0}PZ&TDx_!gD;@`{*yiGkny+zV-#|^~3+?B=gcg
zfS|7o?Un!6`gulR&Wh>lP*?r4X3aWD8>q2^(hhCTFGvGzo`j=62m9fCePGVHXm_W*
z6LCznwx9LWevf!5g;*(t*eQkhFFOrp3|J@PxfEh}6zrnfABtxIv;)I;y_ye_83WpW
zIR>=7B7UmY`ccM7+VbgNKpuckxIR-hePHUZqF<lK()NkCJDndfY1%ehlLttH83XnK
z&o62lf7Z|QZTfn#e&($?2IL3C{qej(^Ah_4@qhY^nDlZi9M9|=`H(zC-z(Z|>r+3+
z0CPtnZcm>PlLyF0qz(S(nip6<`GI|QzWO-^{JzS6;vD{G`*{{(+K)Ivd*8B7#N_F^
z=(j@u7y4bWZp031zm~e{H+{e{;GC+X{)n3Ov#&T85YMRF%yVw?0)1pi56>#uKlF>>
zJ?rNg!1>k!#3wR;HsA|z{!_+}V*NZ1GVM3@^UNIYb@@EkA|E2YQqL2d>*+Hv@?d@H
z=NKS%k>$+<`~mKJ+>>Ha|0((cm@{FXY0@_c?=*PcT5~RNJs?jac2U<)elTY&b@3t3
zF;FJu>COQ41MamJ){j^{g}70D#=~<So;gi{j|tDn&6%F#ddM>g<DbIz*Qb8OUb6n#
z0BytE``G>n%;l3j_vD#0!LxO?#hkC#RzJ^Ej1MP$k2wC0?dKShKM9^+BfeAnf1<u?
zfldJH=l&Cbww~Y_jycDnpALP59M|L8XfW$B+s^tO*JJ!!SPF5p3bDDGXUI2*-(>qO
z0QX<^KjDt^#82U$^fS)Ge#QCX&uM9CKVvL@#(c2@YsV=9YnTSs(o+Q1L=EgMrwnjL
zdkXvSDeTXu5L$3*$dDmF1E(;LenNfRd%5qE|EQ-^{^<rp0GoigD#YL#34{RT7uv!}
zFXzqqV9I7a>|@$f3Es0#zGoSH*9FJ~$d`wJ8>&!*>pb9kAOj!`%wOMRXrzqi2tGH-
z9Y6X3n}Nf?r@*rgcm}^o(+Yt7Kz_ZHiSsWG@wf`HtO_yJ3h}!N@zn~kyb7_oYV5XY
zs#*Ky&mkYjhxspMVtE$C0xQJ!D#Yc&woQ8~Y{IZ>BQ{qdHdr<5*n;&B0P2?FFODi*
zzgtgFg_vJ$pQcTewrRwX!!A8UAs!j$9A1BF{f%KGMC?CoX;C(N;L|ocKkeg)>*k#O
zv+5^5#o^yB&~`oASGA28c5ZDShD{Q&<q9)}JV`nEQjb6Np>V{bE5u$a#JwxTdTSdm
z?bM0SSrlTzm7Bju1@cix;(WQ3$pwW4A}(AZK3pNTT;bp4Ltntw40}1^!xiGem3ud@
zeaPnvaQ(QHiR*$7;?osk%oXC+6=K*GV%Qbp^A%#u6=L00lTL0sk&k`F{Fgd$EldNr
z#?^)1c;~oo;r9Q3Dn7f9T$;wX{JW9R4ssq8+sq_2{=HB?O+zeqxN{R_g#h5H3qTL#
zr5%+#!|NWP0{@y@1$>e!AV1oHeYt{wFOf#~?%gXO6I4LXU50Ns=Q?5^KSSRu`kPY@
zr9Mo#gE9x@UZeX^=b^qqeGfiDkK$Xd*&VlS+ja)=vUYuf@*{n85qGS8Rp^UCe;|HO
zS(W}WkQwaX!$0K=bq~r#lto!T{qd+9Q2%3W4QZgvPv0!+e4yF>J$ww$ApThUtimRs
z<4-8RGe6r!KO@Smh6c6)@{;*Jz<kfPQZ_|wwAMN4gM%1hts9u{StfN3e7_fMryV|U
z&6+i5D5ugt5b@SpA7wvM*5o~P3)7FL4fvk^GFJlaL0=`}vK8X9wOmShkn%9~8v6aP
zFDZXACV}^q=OKgLUsZpT(9qB_=yi`lw_cA}qT3O(R0{d+2A~KsToGiqBJ@Mim@#AK
zL;hZXe?xU4e0-DfJvoy6Qv#5ldw}&oIzU;fw#2o<alHsce6vCvwL%=VLfo=)Z{@iN
zdFs2K1|mip`*R%5zXvMBQLDyHoiF73&WN+tdkbQ+6=JbfV~@u3Q9sxJ`Xq0}Tr0#}
t<1Dh1LOi!>;_o&EA703M+y(i&I)LkWJrixJ=SPE+O2N!r4gB|a{}(hVmSF$@

literal 0
HcmV?d00001

diff --git a/pd/tcl/pd_bindings.tcl b/pd/tcl/pd_bindings.tcl
index 0cef04742..a1c4c57af 100644
--- a/pd/tcl/pd_bindings.tcl
+++ b/pd/tcl/pd_bindings.tcl
@@ -4,83 +4,99 @@ package require pd_menucommands
 package require dialog_find
 
 namespace eval ::pd_bindings:: {
-    variable modifier
-
-    namespace export window_bindings
+    namespace export global_bindings
     namespace export dialog_bindings
-    namespace export canvas_bindings
+    namespace export patch_bindings
 }
 
-# the commands are bound using "" quotations so that the $mytoplevel is
+# TODO rename pd_bindings to window_bindings after merge is done
+
+# Some commands are bound using "" quotations so that the $mytoplevel is
 # interpreted immediately.  Since the command is being bound to $mytoplevel,
 # it makes sense to have value of $mytoplevel already in the command.  This is
-# the opposite of the menu commands in pd_menus.tcl
+# the opposite of most menu/bind commands here and in pd_menus.tcl, which use
+# {} to force execution of any variables (i.e. $::focused_window) until later
+
 
-# binding by class is not recursive, so its useful for certain things
+# binding by class is not recursive, so its useful for window events
 proc ::pd_bindings::class_bindings {} {
     # and the Pd window is in a class to itself
-    bind PdWindow <Configure>              "::pd_bindings::window_configure %W"
     bind PdWindow <FocusIn>                "::pd_bindings::window_focusin %W"
-    # bind to all the canvas windows
-    bind CanvasWindow <Map>                    "::pd_bindings::map %W"
-    bind CanvasWindow <Unmap>                  "::pd_bindings::unmap %W"
-    bind CanvasWindow <Configure>              "::pd_bindings::window_configure %W"
-    bind CanvasWindow <FocusIn>                "::pd_bindings::window_focusin %W"
-    # bindings for dialog windows, which behave differently than canvas windows
+    # bind to all the windows dedicated to patch canvases
+    bind PatchWindow <FocusIn>                "::pd_bindings::window_focusin %W"
+    bind PatchWindow <Map>                    "::pd_bindings::map %W"
+    bind PatchWindow <Unmap>                  "::pd_bindings::unmap %W"
+    bind PatchWindow <Configure> "::pd_bindings::patch_configure %W %w %h %x %y"
+    # dialog panel windows bindings, which behave differently than PatchWindows
     bind DialogWindow <Configure>              "::pd_bindings::dialog_configure %W"
     bind DialogWindow <FocusIn>                "::pd_bindings::dialog_focusin %W"
 }
 
-proc ::pd_bindings::window_bindings {mytoplevel} {
-    variable modifier
-
-    # for key bindings
-    if {$::windowingsystem eq "aqua"} {
-        set modifier "Mod1"
-    } else {
-        set modifier "Control"
-    }
-
-    # File menu
-    bind $mytoplevel <$modifier-Key-b>        "menu_helpbrowser"
-    bind $mytoplevel <$modifier-Key-f>        "::dialog_find::menu_find_dialog $mytoplevel"
-    bind $mytoplevel <$modifier-Key-n>        "menu_new"
-    bind $mytoplevel <$modifier-Key-o>        "menu_open"
-    bind $mytoplevel <$modifier-Key-p>        "menu_print $mytoplevel"
-    bind $mytoplevel <$modifier-Key-q>        "pdsend \"pd verifyquit\""
-    bind $mytoplevel <$modifier-Key-r>        "menu_raise_pdwindow"
-    bind $mytoplevel <$modifier-Shift-Key-L>  "menu_clear_console"
-    bind $mytoplevel <$modifier-Shift-Key-Q>  "pdsend \"pd quit\""
-    bind $mytoplevel <$modifier-Shift-Key-R>  "menu_toggle_console"
-
-    # DSP control
-    bind $mytoplevel <$modifier-Key-slash>    "pdsend \"pd dsp 1\""
-    bind $mytoplevel <$modifier-Key-period>   "pdsend \"pd dsp 0\""
-}
-
-proc ::pd_bindings::pdwindow_bindings {mytoplevel} {
-    variable modifier
-
-    window_bindings $mytoplevel
-
-    # TODO update this to work with the console, if it is used
-    bind $mytoplevel <$modifier-Key-a> ".pdwindow.text tag add sel 1.0 end"
-    bind $mytoplevel <$modifier-Key-x> "tk_textCut .pdwindow.text"
-    bind $mytoplevel <$modifier-Key-c> "tk_textCopy .pdwindow.text"
-    bind $mytoplevel <$modifier-Key-v> "tk_textPaste .pdwindow.text"
-    bind $mytoplevel <$modifier-Key-w> "wm iconify $mytoplevel"
+proc ::pd_bindings::global_bindings {} {
+    # we use 'bind all' everywhere to get as much of Tk's automatic binding
+    # behaviors as possible, things like not sending an event for 'O' when
+    # 'Control-O' is pressed.
+    bind all <$::modifier-Key-a>      {menu_send %W selectall}
+    bind all <$::modifier-Key-b>      {menu_helpbrowser}
+    bind all <$::modifier-Key-c>      {menu_send %W copy}
+    bind all <$::modifier-Key-d>      {menu_send %W duplicate}
+    bind all <$::modifier-Key-e>      {menu_toggle_editmode}
+    bind all <$::modifier-Key-f>      {menu_find_dialog}
+    bind all <$::modifier-Key-g>      {menu_send %W findagain}
+    bind all <$::modifier-Key-n>      {menu_new}
+    bind all <$::modifier-Key-o>      {menu_open}
+    bind all <$::modifier-Key-p>      {menu_print $::focused_window}
+    bind all <$::modifier-Key-q>      {pdsend "pd verifyquit"}
+    bind all <$::modifier-Key-r>      {menu_raise_pdwindow}
+    bind all <$::modifier-Key-s>      {menu_send %W menusave}
+    bind all <$::modifier-Key-v>      {menu_send %W paste}
+    bind all <$::modifier-Key-w>      {menu_send_float %W menuclose 0}
+    bind all <$::modifier-Key-x>      {menu_send %W cut}
+    bind all <$::modifier-Key-z>      {menu_undo}
+    bind all <$::modifier-Key-1>      {menu_send_float %W obj 0}
+    bind all <$::modifier-Key-2>      {menu_send_float %W msg 0}
+    bind all <$::modifier-Key-3>      {menu_send_float %W floatatom 0}
+    bind all <$::modifier-Key-4>      {menu_send_float %W symbolatom 0}
+    bind all <$::modifier-Key-5>      {menu_send_float %W text 0}
+    bind all <$::modifier-Key-slash>  {pdsend "pd dsp 1"}
+    bind all <$::modifier-Key-period> {pdsend "pd dsp 0"}
+    bind all <$::modifier-greater>    {menu_raisenextwindow}
+    bind all <$::modifier-less>       {menu_raisepreviouswindow}
 
+    # annoying, but Tk's bind needs uppercase letter to get the Shift
+    bind all <$::modifier-Shift-Key-B> {menu_send %W bng}
+    bind all <$::modifier-Shift-Key-C> {menu_send %W mycnv}
+    bind all <$::modifier-Shift-Key-D> {menu_send %W vradio}
+    bind all <$::modifier-Shift-Key-H> {menu_send %W hslider}
+    bind all <$::modifier-Shift-Key-I> {menu_send %W hradio}
+    bind all <$::modifier-Shift-Key-L> {menu_clear_console}
+    bind all <$::modifier-Shift-Key-N> {menu_send %W numbox}
+    bind all <$::modifier-Shift-Key-Q> {pdsend "pd quit"}
+    bind all <$::modifier-Shift-Key-S> {menu_send %W menusaveas}
+    bind all <$::modifier-Shift-Key-T> {menu_send %W toggle}
+    bind all <$::modifier-Shift-Key-U> {menu_send %W vumeter}
+    bind all <$::modifier-Shift-Key-V> {menu_send %W vslider}
+    bind all <$::modifier-Shift-Key-W> {menu_send_float %W menuclose 1}
+    bind all <$::modifier-Shift-Key-Z> {menu_redo}
+
+    # OS-specific bindings
     if {$::windowingsystem eq "aqua"} {
-        bind $mytoplevel <$modifier-Key-m>   "menu_minimize $mytoplevel"
-        bind $mytoplevel <$modifier-Key-t>   "menu_font_dialog $mytoplevel"
-        bind $mytoplevel <$modifier-quoteleft> "menu_raisenextwindow"
+        # Cmd-m = Minimize and Cmd-t = Font on Mac OS X for all apps
+        bind all <$::modifier-Key-m>       {menu_minimize %W}
+        bind all <$::modifier-Key-t>       {menu_font_dialog}
+        bind all <$::modifier-quoteleft>   {menu_raisenextwindow}
+        bind all <$::modifier-Shift-Key-M> {menu_message_dialog}
     } else {
-        bind $mytoplevel <$modifier-Key-m>    "menu_message_dialog"
-        bind $mytoplevel <$modifier-Key-t>    "menu_texteditor"
+        bind all <$::modifier-Key-m>       {menu_message_dialog}
+        #bind all <$::modifier-Key-t>       {menu_texteditor}
+        bind all <$::modifier-Next>        {menu_raisenextwindow}    ;# PgUp
+        bind all <$::modifier-Prior>       {menu_raisepreviouswindow};# PageDown
     }
 
-    # Tcl event bindings
-    wm protocol $mytoplevel WM_DELETE_WINDOW "pdsend \"pd verifyquit\""
+    bind all <KeyPress>         {::pd_bindings::sendkey %W 1 %K %A 0}
+    bind all <KeyRelease>       {::pd_bindings::sendkey %W 0 %K %A 0}
+    bind all <Shift-KeyPress>   {::pd_bindings::sendkey %W 1 %K %A 1}
+    bind all <Shift-KeyRelease> {::pd_bindings::sendkey %W 0 %K %A 1}
 }
 
 # this is for the dialogs: find, font, sendmessage, gatom properties, array
@@ -89,135 +105,115 @@ proc ::pd_bindings::pdwindow_bindings {mytoplevel} {
 proc ::pd_bindings::dialog_bindings {mytoplevel dialogname} {
     variable modifier
 
-    window_bindings $mytoplevel
-
     bind $mytoplevel <KeyPress-Escape> "dialog_${dialogname}::cancel $mytoplevel"
     bind $mytoplevel <KeyPress-Return> "dialog_${dialogname}::ok $mytoplevel"
-    bind $mytoplevel <$modifier-Key-w> "dialog_${dialogname}::cancel $mytoplevel"
+    bind $mytoplevel <$::modifier-Key-w> "dialog_${dialogname}::cancel $mytoplevel"
+    # these aren't supported in the dialog, so alert the user, then break so
+    # that no other key bindings are run
+    bind $mytoplevel <$::modifier-Key-s>       {bell; break}
+    bind $mytoplevel <$::modifier-Shift-Key-S> {bell; break}
+    bind $mytoplevel <$::modifier-Key-p>       {bell; break}
 
-    $mytoplevel configure -padx 10 -pady 5
-    wm group $mytoplevel .
-    wm resizable $mytoplevel 0 0
     wm protocol $mytoplevel WM_DELETE_WINDOW "dialog_${dialogname}::cancel $mytoplevel"
-    catch { # not all platforms/Tcls versions have these options
-        wm attributes $mytoplevel -topmost 1
-        #wm attributes $mytoplevel -transparent 1
-        #$mytoplevel configure -highlightthickness 1
-    }
 }
 
-proc ::pd_bindings::canvas_bindings {mytoplevel} {
+proc ::pd_bindings::patch_bindings {mytoplevel} {
     variable modifier
-    set mycanvas $mytoplevel.c
-
-    window_bindings $mytoplevel
+    set tkcanvas [tkcanvas_name $mytoplevel]
 
-    # key bindings -------------------------------------------------------------
-    bind $mytoplevel <$modifier-Key-1>        "pdsend \"$mytoplevel obj\""
-    bind $mytoplevel <$modifier-Key-2>        "pdsend \"$mytoplevel msg\""
-    bind $mytoplevel <$modifier-Key-3>        "pdsend \"$mytoplevel floatatom\""
-    bind $mytoplevel <$modifier-Key-4>        "pdsend \"$mytoplevel symbolatom\""
-    bind $mytoplevel <$modifier-Key-5>        "pdsend \"$mytoplevel text\""
-    bind $mytoplevel <$modifier-Key-a>        "pdsend \"$mytoplevel selectall\""
-    bind $mytoplevel <$modifier-Key-c>        "pdsend \"$mytoplevel copy\""
-    bind $mytoplevel <$modifier-Key-d>        "pdsend \"$mytoplevel duplicate\""
-    bind $mytoplevel <$modifier-Key-e>        "pdsend \"$mytoplevel editmode 0\""
-    bind $mytoplevel <$modifier-Key-g>        "pdsend \"$mytoplevel findagain\""
-    bind $mytoplevel <$modifier-Key-s>        "pdsend \"$mytoplevel menusave\""
-    bind $mytoplevel <$modifier-Key-v>        "pdsend \"$mytoplevel paste\""
-    bind $mytoplevel <$modifier-Key-w>        "pdsend \"$mytoplevel menuclose 0\""
-    bind $mytoplevel <$modifier-Key-x>        "pdsend \"$mytoplevel cut\""
-    bind $mytoplevel <$modifier-Key-z>        "menu_undo $mytoplevel"
+    # TODO move mouse bindings to global and bind to 'all'
 
-    # annoying, but Tk's bind needs uppercase letter to get the Shift
-    bind $mytoplevel <$modifier-Shift-Key-B> "pdsend \"$mytoplevel bng 1\""
-    bind $mytoplevel <$modifier-Shift-Key-C> "pdsend \"$mytoplevel mycnv 1\""
-    bind $mytoplevel <$modifier-Shift-Key-D> "pdsend \"$mytoplevel vradio 1\""
-    bind $mytoplevel <$modifier-Shift-Key-H> "pdsend \"$mytoplevel hslider 1\""
-    bind $mytoplevel <$modifier-Shift-Key-I> "pdsend \"$mytoplevel hradio 1\""
-    bind $mytoplevel <$modifier-Shift-Key-N> "pdsend \"$mytoplevel numbox 1\""
-    bind $mytoplevel <$modifier-Shift-Key-S> "pdsend \"$mytoplevel menusaveas\""
-    bind $mytoplevel <$modifier-Shift-Key-T> "pdsend \"$mytoplevel toggle 1\""
-    bind $mytoplevel <$modifier-Shift-Key-U> "pdsend \"$mytoplevel vumeter 1\""
-    bind $mytoplevel <$modifier-Shift-Key-V> "pdsend \"$mytoplevel vslider 1\""
-    bind $mytoplevel <$modifier-Shift-Key-W> "pdsend \"$mytoplevel menuclose 1\""
-    bind $mytoplevel <$modifier-Shift-Key-Z> "menu_redo $mytoplevel"
-    
-    if {$::windowingsystem eq "aqua"} {
-        bind $mytoplevel <$modifier-Key-m>   "menu_minimize $mytoplevel"
-        bind $mytoplevel <$modifier-Key-t>   "menu_font_dialog $mytoplevel"
-        bind $mytoplevel <$modifier-quoteleft> "menu_raisenextwindow"
-    } else {
-        bind $mytoplevel <$modifier-Key-m>    "menu_message_dialog"
-        bind $mytoplevel <$modifier-Key-t>    "menu_texteditor"
+    # mouse bindings -----------------------------------------------------------
+    # these need to be bound to $tkcanvas because %W will return $mytoplevel for
+    # events over the window frame and $tkcanvas for events over the canvas
+    bind $tkcanvas <Motion>                   "pdtk_canvas_motion %W %x %y 0"
+    bind $tkcanvas <$::modifier-Motion>         "pdtk_canvas_motion %W %x %y 2"
+    bind $tkcanvas <ButtonPress-1>            "pdtk_canvas_mouse %W %x %y %b 0"
+    bind $tkcanvas <ButtonRelease-1>          "pdtk_canvas_mouseup %W %x %y %b"
+    bind $tkcanvas <$::modifier-ButtonPress-1>  "pdtk_canvas_mouse %W %x %y %b 2"
+    bind $tkcanvas <Shift-ButtonPress-1>        "pdtk_canvas_mouse %W %x %y %b 1"
+
+    if {$::windowingsystem eq "x11"} {
+        # from http://wiki.tcl.tk/3893
+        bind all <Button-4> \
+            {event generate [focus -displayof %W] <MouseWheel> -delta  1}
+        bind all <Button-5> \
+            {event generate [focus -displayof %W] <MouseWheel> -delta -1}
+        bind all <Shift-Button-4> \
+            {event generate [focus -displayof %W] <Shift-MouseWheel> -delta  1}
+        bind all <Shift-Button-5> \
+            {event generate [focus -displayof %W] <Shift-MouseWheel> -delta -1}
     }
-    bind $mytoplevel <KeyPress>         "::pd_bindings::sendkey %W 1 %K %A 0"
-    bind $mytoplevel <KeyRelease>       "::pd_bindings::sendkey %W 0 %K %A 0"
-    bind $mytoplevel <Shift-KeyPress>   "::pd_bindings::sendkey %W 1 %K %A 1"
-    bind $mytoplevel <Shift-KeyRelease> "::pd_bindings::sendkey %W 0 %K %A 1"
+    bind $tkcanvas <MouseWheel>       {::pdtk_canvas::scroll %W y %D}
+    bind $tkcanvas <Shift-MouseWheel> {::pdtk_canvas::scroll %W x %D}
 
-    # mouse bindings -----------------------------------------------------------
-    # these need to be bound to $mycanvas because %W will return $mytoplevel for
-    # events over the window frame and $mytoplevel.c for events over the canvas
-    bind $mycanvas <Motion>                   "pdtk_canvas_motion %W %x %y 0"
-    bind $mycanvas <$modifier-Motion>         "pdtk_canvas_motion %W %x %y 2"
-    bind $mycanvas <ButtonPress-1>            "pdtk_canvas_mouse %W %x %y %b 0"
-    bind $mycanvas <ButtonRelease-1>          "pdtk_canvas_mouseup %W %x %y %b"
-    bind $mycanvas <$modifier-ButtonPress-1>  "pdtk_canvas_mouse %W %x %y %b 2"
-    # TODO look into "virtual events' for a means for getting Shift-Button, etc.
+    # "right clicks" are defined differently on each platform
     switch -- $::windowingsystem { 
         "aqua" {
-            bind $mycanvas <ButtonPress-2>      "pdtk_canvas_rightclick %W %x %y %b"
+            bind $tkcanvas <ButtonPress-2>      "pdtk_canvas_rightclick %W %x %y %b"
             # on Mac OS X, make a rightclick with Ctrl-click for 1 button mice
-            bind $mycanvas <Control-Button-1> "pdtk_canvas_rightclick %W %x %y %b"
-            # TODO try replacing the above with this
-            #bind all <Control-Button-1> {event generate %W <Button-2> \
-            #                                      -x %x -y %y -rootx %X -rooty %Y \
-            #                                      -button 2 -time %t}
+            bind $tkcanvas <Control-Button-1> "pdtk_canvas_rightclick %W %x %y %b"
+            bind $tkcanvas <Option-ButtonPress-1> "pdtk_canvas_mouse %W %x %y %b 3"    
         } "x11" {
-            bind $mycanvas <ButtonPress-3>    "pdtk_canvas_rightclick %W %x %y %b"
+            bind $tkcanvas <ButtonPress-3>    "pdtk_canvas_rightclick %W %x %y %b"
             # on X11, button 2 "pastes" from the X windows clipboard
-            bind $mycanvas <ButtonPress-2>   "pdtk_canvas_clickpaste %W %x %y %b"
+            bind $tkcanvas <ButtonPress-2>   "pdtk_canvas_clickpaste %W %x %y %b"
+            bind $tkcanvas <Alt-ButtonPress-1> "pdtk_canvas_mouse %W %x %y %b 3"
         } "win32" {
-            bind $mycanvas <ButtonPress-3>   "pdtk_canvas_rightclick %W %x %y %b"
+            bind $tkcanvas <ButtonPress-3>   "pdtk_canvas_rightclick %W %x %y %b"
+            bind $tkcanvas <Alt-ButtonPress-1> "pdtk_canvas_mouse %W %x %y %b 3"
         }
     }
-    #TODO bind $mytoplevel <MouseWheel>
 
     # window protocol bindings
     wm protocol $mytoplevel WM_DELETE_WINDOW "pdsend \"$mytoplevel menuclose 0\""
-    bind $mycanvas <Destroy> "::pd_bindings::window_destroy %W"
+    bind $tkcanvas <Destroy> "::pd_bindings::window_destroy %W"
 }
 
 
 #------------------------------------------------------------------------------#
 # event handlers
 
-proc ::pd_bindings::window_configure {mytoplevel} {
-    pdtk_canvas_getscroll $mytoplevel
+proc ::pd_bindings::patch_configure {mytoplevel width height x y} {
+    # for some reason, when we create a window, we get an event with a
+    # widthXheight of 1x1 first, then we get the right values, so filter it out
+    if {$width == 1 && $height == 1} {return}
+    pdtk_canvas_getscroll [tkcanvas_name $mytoplevel]
+    # send the size/location of the window and canvas to 'pd' in the form of:
+    #    left top right bottom
+    pdsend "$mytoplevel setbounds $x $y [expr $x + $width] [expr $y + $height]"
 }
     
-proc ::pd_bindings::window_destroy {mycanvas} {
-    set mytoplevel [winfo toplevel $mycanvas]
+proc ::pd_bindings::window_destroy {window} {
+    set mytoplevel [winfo toplevel $window]
     unset ::editmode($mytoplevel)
+    unset ::editingtext($mytoplevel)
+    unset ::loaded($mytoplevel)
+    # unset my entries all of the window data tracking arrays
+    array unset ::windowname $mytoplevel
+    array unset ::parentwindows $mytoplevel
+    array unset ::childwindows $mytoplevel
 }
 
 # do tasks when changing focus (Window menu, scrollbars, etc.)
 proc ::pd_bindings::window_focusin {mytoplevel} {
-    # pdtk_post "::pd_bindings::window_focusin $mytoplevel"
+    # focused_window is used throughout for sending bindings, menu commands,
+    # etc. to the correct patch receiver symbol.
     set ::focused_window $mytoplevel
-    ::dialog_find::set_canvas_to_search $mytoplevel
-    ::pd_menucommands::set_menu_new_dir $mytoplevel
+    ::dialog_find::set_window_to_search $mytoplevel
+    ::pd_menucommands::set_filenewdir $mytoplevel
     ::dialog_font::update_font_dialog $mytoplevel
     if {$mytoplevel eq ".pdwindow"} {
         ::pd_menus::configure_for_pdwindow 
     } else {
         ::pd_menus::configure_for_canvas $mytoplevel
     }
-    # TODO handle enabling/disabling the Undo and Redo menu items in Edit
+    if {[winfo exists .font]} {wm transient .font $::focused_window}
+    # if we regain focus from another app, make sure to editmode cursor is right
+    if {$::editmode($mytoplevel)} {
+        $mytoplevel configure -cursor hand2
+    }
     # TODO handle enabling/disabling the Cut/Copy/Paste menu items in Edit
-    # TODO enable menu items that the Pd window or dialogs might have disabled
-    # TODO update "Open Recent" menu
 }
 
 proc ::pd_bindings::dialog_configure {mytoplevel} {
@@ -233,6 +229,7 @@ proc ::pd_bindings::dialog_focusin {mytoplevel} {
 # don't get a final "unmap" event when we destroy the window.
 proc ::pd_bindings::map {mytoplevel} {
     pdsend "$mytoplevel map 1"
+    ::pdtk_canvas::finished_loading_file $mytoplevel
 }
 
 proc ::pd_bindings::unmap {mytoplevel} {
@@ -243,7 +240,11 @@ proc ::pd_bindings::unmap {mytoplevel} {
 #------------------------------------------------------------------------------#
 # key usage
 
-proc ::pd_bindings::sendkey {mycanvas state key iso shift} {
+# canvas_key() expects to receive the patch's mytoplevel because key messages
+# are local to each patch.  Therefore, key messages are not send for the
+# dialog panels, the Pd window, help browser, etc. so we need to filter those
+# events out.
+proc ::pd_bindings::sendkey {window state key iso shift} {
     # TODO canvas_key on the C side should be refactored with this proc as well
     switch -- $key {
         "BackSpace" { set iso ""; set key 8    }
@@ -257,7 +258,13 @@ proc ::pd_bindings::sendkey {mycanvas state key iso shift} {
     if {$iso ne ""} {
         scan $iso %c key
     }
-    puts "::pd_bindings::sendkey {%W:$mycanvas $state %K$key %A$iso $shift}"
-    # $mycanvas might be a toplevel, but [winfo toplevel] does the right thing
-    pdsend "[winfo toplevel $mycanvas] key $state $key $shift"
+    # some pop-up panels also bind to keys like the enter, but then disappear,
+    # so ignore their events.  The inputbox in the Startup dialog does this.
+    if {! [winfo exists $window]} {return}
+    #$window might be a toplevel or canvas, [winfo toplevel] does the right thing
+    set mytoplevel [winfo toplevel $window]
+    if {[winfo class $mytoplevel] eq "PatchWindow"} {
+        pdsend "$mytoplevel key $state $key $shift"
+    }
+    # TODO send to 'pd key' for global key events in Pd?
 }
diff --git a/pd/tcl/pd_connect.tcl b/pd/tcl/pd_connect.tcl
index cdd3d91db..4fa136dd7 100644
--- a/pd/tcl/pd_connect.tcl
+++ b/pd/tcl/pd_connect.tcl
@@ -3,6 +3,7 @@ package provide pd_connect 0.1
 
 namespace eval ::pd_connect:: {
     variable pd_socket
+    variable cmds_from_pd ""
 
     namespace export to_pd
     namespace export create_socket
@@ -12,18 +13,17 @@ namespace eval ::pd_connect:: {
 # TODO figure out how to escape { } properly
 
 proc ::pd_connect::configure_socket {sock} {
-    fconfigure $sock -blocking 0 -buffering line -encoding utf-8;
-    fileevent $sock readable {::pd_connect::pd_readsocket ""}
+    fconfigure $sock -blocking 0 -buffering none -encoding utf-8;
+    fileevent $sock readable {::pd_connect::pd_readsocket}
 }
 
 # if pd opens first, it starts pd-gui, then pd-gui connects to the port pd sent
-proc ::pd_connect::to_pd {port} {
-    # puts "::pd_connect::to_pd"
+proc ::pd_connect::to_pd {port {host localhost}} {
     variable pd_socket
-    # puts stderr "Connecting to localhost $port ..."
-    if {[catch {set pd_socket [socket localhost $port]}]} {
-        puts stderr "WARNING: connect to pd failed, retrying port $port."
-        after 1000 ::pd_connect::to_pd $port
+    ::pdwindow::debug "'pd-gui' connecting to 'pd' on localhost $port ...\n"
+    if {[catch {set pd_socket [socket $host $port]}]} {
+        puts stderr "WARNING: connect to pd failed, retrying port $host:$port."
+        after 1000 ::pd_connect::to_pd $port $host
         return
     }
     ::pd_connect::configure_socket $pd_socket
@@ -40,15 +40,15 @@ proc ::pd_connect::create_socket {} {
 }
 
 proc ::pd_connect::from_pd {channel clientaddr clientport} {
-    puts "::pd_connect::from_pd"
     variable pd_socket $channel
-    puts "Connection from $clientaddr:$clientport registered"
+    ::pdwindow::debug "Connection from 'pd' to 'pd-gui' on $clientaddr:$clientport\n"
     ::pd_connect::configure_socket $pd_socket
 }
 
 # send a pd/FUDI message from Tcl to Pd. This function aims to behave like a
-# [; message( in Pd.  Basically, whatever is in quotes after the proc name
-# will be sent as if it was sent from a message box with a leading semi-colon
+# [; message( in Pd or pdsend on the command line.  Basically, whatever is in
+# quotes after the proc name will be sent as if it was sent from a message box
+# with a leading semi-colon.
 proc ::pd_connect::pdsend {message} {
     variable pd_socket
     append message \;
@@ -58,35 +58,39 @@ proc ::pd_connect::pdsend {message} {
     }
 }
 
-proc ::pd_connect::pd_readsocket {cmd_from_pd} {
-    variable pd_socket
-    if {[eof $pd_socket]} {
-        # if we lose the socket connection, that means pd quit, so we quit
-        close $pd_socket
-        exit
-    } 
-    append cmd_from_pd [read $pd_socket]
-    while {![info complete $cmd_from_pd] || \
-        [string index $cmd_from_pd end] ne "\n"} {
-        append cmd_from_pd [read $pd_socket]
-        if {[eof $pd_socket]} {
-        close $pd_socket
-        exit
-        }
-    }
-#        puts stderr [concat CMD: $cmd_from_pd :CMD]
-    if {[catch {uplevel #0 $cmd_from_pd} errorname]} {
-        global errorInfo
-        puts stderr "errorname: >>$errorname<<"
-        switch -regexp -- $errorname { 
-            "missing close-brace" {
-                # TODO consider using [info complete $cmd_from_pd] in a loop
-                pd_readsocket $cmd_from_pd
-            } "^invalid command name" {
-                puts stderr "INVALID COMMAND NAME: $errorInfo"
-            } default {
-                puts stderr "UNHANDLED ERROR: $errorInfo"
-            }
-        }
-    }
+proc ::pd_connect::pd_readsocket {} {
+     variable pd_socket
+     variable cmds_from_pd
+     if {[eof $pd_socket]} {
+         # if we lose the socket connection, that means pd quit, so we quit
+         close $pd_socket
+         exit
+     } 
+     append cmds_from_pd [read $pd_socket]
+     if {[string index $cmds_from_pd end] ne "\n" || \
+             ![info complete $cmds_from_pd]} {
+         # the block is incomplete, wait for the next block of data
+         return
+     } else {
+         set docmds $cmds_from_pd
+         set cmds_from_pd ""
+         if {![catch {uplevel #0 $docmds} errorname]} {
+             # we ran the command block without error, reset the buffer
+         } else {
+             # oops, error, alert the user:
+             global errorInfo
+             switch -regexp -- $errorname {
+                 "missing close-brace" {
+                     ::pdwindow::fatal \
+                         [concat [_ "(Tcl) MISSING CLOSE-BRACE '\}': "] $errorInfo "\n"]
+                 } "^invalid command name" {
+                     ::pdwindow::fatal \
+                         [concat [_ "(Tcl) INVALID COMMAND NAME: "] $errorInfo "\n"]
+                 } default {
+                     ::pdwindow::fatal \
+                         [concat [_ "(Tcl) UNHANDLED ERROR: "] $errorInfo "\n"]
+                 }
+             }
+         }
+     }
 }
diff --git a/pd/tcl/pd_guiprefs.tcl b/pd/tcl/pd_guiprefs.tcl
new file mode 100644
index 000000000..2423441ee
--- /dev/null
+++ b/pd/tcl/pd_guiprefs.tcl
@@ -0,0 +1,249 @@
+#
+# Copyright (c) 1997-2009 Miller Puckette.
+# Copyright (c) 2011 Yvan Volochine.
+#(c) 2008 WordTech Communications LLC. License: standard Tcl license, http://www.tcl.tk/software/tcltk/license.html
+
+package provide pd_guiprefs 0.1
+
+
+namespace eval ::pd_guiprefs:: {
+    namespace export init
+    namespace export write_recentfiles
+    namespace export update_recentfiles
+}
+
+# FIXME should these be globals ?
+set ::recentfiles_key ""
+set ::recentfiles_domain ""
+
+
+#################################################################
+# global procedures
+#################################################################
+# ------------------------------------------------------------------------------
+# init preferences
+#
+proc ::pd_guiprefs::init {} {
+    switch -- $::windowingsystem {
+        "aqua"  { init_aqua }
+        "win32" { init_win }
+        "x11"   { init_x11 }
+    }
+    # assign gui preferences
+    # osx special case for arrays
+    set arr [expr { $::windowingsystem eq "aqua" }]
+    set ::recentfiles_list ""
+    catch {set ::recentfiles_list [get_config $::recentfiles_domain \
+        $::recentfiles_key $arr]}
+}
+
+proc ::pd_guiprefs::init_aqua {} {
+    # osx has a "Open Recent" menu with 10 recent files (others have 5 inlined)
+    set ::recentfiles_domain org.puredata
+    set ::recentfiles_key "NSRecentDocuments"
+    set ::total_recentfiles 10
+}
+
+proc ::pd_guiprefs::init_win {} {
+    # windows uses registry
+    set ::recentfiles_domain "HKEY_CURRENT_USER\\Software\\Pure-Data"
+    set ::recentfiles_key "RecentDocs"
+}
+
+proc ::pd_guiprefs::init_x11 {} {
+    # linux uses ~/.config/pure-data dir
+    set ::recentfiles_domain "~/.config/pure-data"
+    set ::recentfiles_key "recentfiles.conf"
+    prepare_configdir
+}
+
+# ------------------------------------------------------------------------------
+# write recent files
+#
+proc ::pd_guiprefs::write_recentfiles {} {
+    write_config $::recentfiles_list $::recentfiles_domain $::recentfiles_key true
+}
+
+# ------------------------------------------------------------------------------
+# this is called when opening a document (wheredoesthisshouldgo.tcl)
+#
+proc ::pd_guiprefs::update_recentfiles {afile} {
+    # remove duplicates first
+    set index [lsearch -exact $::recentfiles_list $afile]
+    set ::recentfiles_list [lreplace $::recentfiles_list $index $index]
+    # insert new one in the beginning and crop the list
+    set ::recentfiles_list [linsert $::recentfiles_list 0 $afile]
+    set ::recentfiles_list [lrange $::recentfiles_list 0 $::total_recentfiles]
+    ::pd_menus::update_recentfiles_menu
+}
+
+#################################################################
+# main read/write procedures
+#################################################################
+
+# ------------------------------------------------------------------------------
+# get configs from a file or the registry
+#
+proc ::pd_guiprefs::get_config {adomain {akey} {arr}} {
+    switch -- $::windowingsystem {
+        "aqua"  { set conf [get_config_aqua $adomain $akey $arr] }
+        "win32" { set conf [get_config_win $adomain $akey $arr] }
+        "x11"   { set conf [get_config_x11 $adomain $akey $arr] }
+    }
+    return $conf
+}
+
+# ------------------------------------------------------------------------------
+# write configs to a file or to the registry
+# $arr is true if the data needs to be written in an array
+#
+proc ::pd_guiprefs::write_config {data {adomain} {akey} {arr false}} {
+    switch -- $::windowingsystem {
+        "aqua"  { write_config_aqua $data $adomain $akey $arr }
+        "win32" { write_config_win $data $adomain $akey $arr }
+        "x11"   { write_config_x11 $data $adomain $akey }
+    }
+}
+
+#################################################################
+# os specific procedures
+#################################################################
+
+# ------------------------------------------------------------------------------
+# osx: read a plist file
+#
+proc ::pd_guiprefs::get_config_aqua {adomain {akey} {arr false}} {
+    if {![catch {exec defaults read $adomain $akey} conf]} {
+        if {$arr} {
+            set conf [plist_array_to_tcl_list $conf]
+        }
+    } else {
+        # initialize NSRecentDocuments with an empty array
+        exec defaults write $adomain $akey -array
+        set conf {}
+    }
+    return $conf
+}
+
+# ------------------------------------------------------------------------------
+# win: read in the registry
+#
+proc ::pd_guiprefs::get_config_win {adomain {akey} {arr false}} {
+    package require registry
+    if {![catch {registry get $adomain $akey} conf]} {
+        return [expr {$conf}]
+    } else {
+        return {}
+    }
+}
+
+# ------------------------------------------------------------------------------
+# linux: read a config file and return its lines splitted.
+#
+proc ::pd_guiprefs::get_config_x11 {adomain {akey} {arr false}} {
+    set filename [file join $adomain $akey]
+    set conf {}
+    if {
+        [file exists $filename] == 1
+        && [file readable $filename]
+    } {
+        set fl [open $filename r]
+        while {[gets $fl line] >= 0} {
+           lappend conf $line
+        }
+        close $fl
+    }
+    return $conf
+}
+
+# ------------------------------------------------------------------------------
+# osx: write configs to plist file
+# if $arr is true, we write an array
+#
+proc ::pd_guiprefs::write_config_aqua {data {adomain} {akey} {arr false}} {
+    # FIXME empty and write again so we don't loose the order
+    if {[catch {exec defaults write $adomain $akey -array} errorMsg]} {
+        ::pdwindow::error "write_config_aqua $akey: $errorMsg"
+    }
+    if {$arr} {
+        foreach filepath $data {
+            set escaped [escape_for_plist $filepath]
+            exec defaults write $adomain $akey -array-add "$escaped"
+        }
+    } else {
+        set escaped [escape_for_plist $data]
+        exec defaults write $adomain $akey '$escaped'
+    }
+}
+
+# ------------------------------------------------------------------------------
+# win: write configs to registry
+# if $arr is true, we write an array
+#
+proc ::pd_guiprefs::write_config_win {data {adomain} {akey} {arr false}} {
+    package require registry
+    # FIXME: ugly
+    if {$arr} {
+        if {[catch {registry set $adomain $akey $data multi_sz} errorMsg]} {
+            ::pdwindow::error "write_config_win $data $akey: $errorMsg"
+        }
+    } else {
+        if {[catch {registry set $adomain $akey $data sz} errorMsg]} {
+            ::pdwindow::error "write_config_win $data $akey: $errorMsg"
+        }
+    }
+}
+
+# ------------------------------------------------------------------------------
+# linux: write configs to USER_APP_CONFIG_DIR
+#
+proc ::pd_guiprefs::write_config_x11 {data {adomain} {akey}} {
+    # right now I (yvan) assume that data are just \n separated, i.e. no keys
+    set data [join $data "\n"]
+    set filename [file join $adomain $akey]
+    if {[catch {set fl [open $filename w]} errorMsg]} {
+        ::pdwindow::error "write_config_x11 $data $akey: $errorMsg"
+    } else {
+        puts -nonewline $fl $data
+        close $fl
+    }
+}
+
+#################################################################
+# utils
+#################################################################
+
+# ------------------------------------------------------------------------------
+# linux only! : look for pd config directory and create it if needed
+#
+proc ::pd_guiprefs::prepare_configdir {} {
+    if {[file isdirectory $::recentfiles_domain] != 1} {
+        file mkdir $::recentfiles_domain
+        ::pdwindow::debug "$::recentfiles_domain was created.\n"
+    }
+}
+
+# ------------------------------------------------------------------------------
+# osx: handles arrays in plist files (thanks hc)
+#
+proc ::pd_guiprefs::plist_array_to_tcl_list {arr} {
+    set result {}
+    set filelist $arr
+    regsub -all -- {("?),\s+("?)} $filelist {\1 \2} filelist
+    regsub -all -- {\n} $filelist {} filelist
+    regsub -all -- {^\(} $filelist {} filelist
+    regsub -all -- {\)$} $filelist {} filelist
+    regsub -line -- {^'(.*)'$} $filelist {\1} filelist
+
+    foreach file $filelist {
+        set filename [regsub -- {,$} $file {}]
+        lappend result $filename
+    }
+    return $result
+}
+
+# the Mac OS X 'defaults' command uses single quotes to quote things,
+# so they need to be escaped
+proc ::pd_guiprefs::escape_for_plist {str} {
+    return [regsub -all -- {'} $str {\\'}]
+}
diff --git a/pd/tcl/pd_menucommands.tcl b/pd/tcl/pd_menucommands.tcl
index e1373b84c..91f562f37 100644
--- a/pd/tcl/pd_menucommands.tcl
+++ b/pd/tcl/pd_menucommands.tcl
@@ -3,8 +3,6 @@ package provide pd_menucommands 0.1
 
 namespace eval ::pd_menucommands:: {
     variable untitled_number "1"
-    variable menu_new_dir [pwd]
-    variable menu_open_dir [pwd]
 
     namespace export menu_*
 }
@@ -14,27 +12,25 @@ namespace eval ::pd_menucommands:: {
 
 proc ::pd_menucommands::menu_new {} {
     variable untitled_number
-    variable menu_new_dir
-    if { ! [file isdirectory $menu_new_dir]} {set menu_new_dir $::env(HOME)}
-    set untitled_name [_ "Untitled"]
-    pdsend "pd filename $untitled_name-$untitled_number [enquote_path $menu_new_dir]"
-    pdsend "#N canvas"
-    pdsend "#X pop 1"
+    if { ! [file isdirectory $::filenewdir]} {set ::filenewdir $::env(HOME)}
+    # to localize "Untitled" there will need to be changes in g_canvas.c and
+    # g_readwrite.c, where it tests for the string "Untitled"
+    set untitled_name "Untitled"
+    pdsend "pd menunew $untitled_name-$untitled_number [enquote_path $::filenewdir]"
     incr untitled_number
 }
 
 proc ::pd_menucommands::menu_open {} {
-    variable menu_open_dir
-    if { ! [file isdirectory $menu_open_dir]} {set menu_open_dir $::env(HOME)}
+    if { ! [file isdirectory $::fileopendir]} {set ::fileopendir $::env(HOME)}
     set files [tk_getOpenFile -defaultextension .pd \
                        -multiple true \
                        -filetypes $::filetypes \
-                       -initialdir $menu_open_dir]
+                       -initialdir $::fileopendir]
     if {$files ne ""} {
         foreach filename $files { 
             open_file $filename
         }
-        set menu_open_dir [file dirname $filename]
+        set ::fileopendir [file dirname $filename]
     }
 }
 
@@ -43,54 +39,83 @@ proc ::pd_menucommands::menu_print {mytoplevel} {
                       -defaultextension .ps \
                       -filetypes { {{postscript} {.ps}} }]
     if {$filename ne ""} {
-        $mytoplevel.c postscript -file $filename 
+        set tkcanvas [tkcanvas_name $mytoplevel]
+        $tkcanvas postscript -file $filename 
     }
 }
 
-# dialog types:
-#   global (only one):   find, sendmessage, prefs, helpbrowser
-#   per-canvas:          font, canvas properties (created with a message from pd)
-#   per object:          gatom, iemgui, array, data structures (created with a message from pd)
+# ------------------------------------------------------------------------------
+# functions called from Edit menu
+
+proc ::pd_menucommands::menu_undo {} {
+    if {$::focused_window eq $::undo_toplevel && $::undo_action ne "no"} {
+        pdsend "$::focused_window undo"
+    }
+}
+
+proc ::pd_menucommands::menu_redo {} {
+    if {$::focused_window eq $::undo_toplevel && $::redo_action ne "no"} {
+        pdsend "$::focused_window redo"
+    }
+}
+
+proc ::pd_menucommands::menu_editmode {state} {
+    if {[winfo class $::focused_window] ne "PatchWindow"} {return}
+    set ::editmode_button $state
+# this shouldn't be necessary because 'pd' will reply with pdtk_canvas_editmode
+#    set ::editmode($::focused_window) $state
+    pdsend "$::focused_window editmode $state"
+}
 
+proc ::pd_menucommands::menu_toggle_editmode {} {
+    menu_editmode [expr {! $::editmode_button}]
+}
 
 # ------------------------------------------------------------------------------
-# functions called from Edit menu
+# generic procs for sending menu events
 
-proc menu_undo {mytoplevel} {
-    # puts stderr "menu_undo $mytoplevel not implemented yet"
+# send a message to a pd canvas receiver
+proc ::pd_menucommands::menu_send {window message} {
+    set mytoplevel [winfo toplevel $window]
+    if {[winfo class $mytoplevel] eq "PatchWindow"} {
+        pdsend "$mytoplevel $message"
+    } elseif {$mytoplevel eq ".pdwindow"} {
+        if {$message eq "copy"} {
+            tk_textCopy .pdwindow.text
+        } elseif {$message eq "selectall"} {
+            .pdwindow.text tag add sel 1.0 end
+        } elseif {$message eq "menusaveas"} {
+            ::pdwindow::save_logbuffer_to_file
+        }
+    }
 }
 
-proc menu_redo {mytoplevel} {
-    # puts stderr "menu_redo $mytoplevel not implemented yet"
+# send a message to a pd canvas receiver with a float arg
+proc ::pd_menucommands::menu_send_float {window message float} {
+    set mytoplevel [winfo toplevel $window]
+    if {[winfo class $mytoplevel] eq "PatchWindow"} {
+        pdsend "$mytoplevel $message $float"
+    }
 }
 
 # ------------------------------------------------------------------------------
 # open the dialog panels
 
 proc ::pd_menucommands::menu_message_dialog {} {
-    if {[winfo exists .send_message]} {
-        wm deiconify .send_message
-        raise .message
-    } else {
-        # TODO insert real message panel here
-        toplevel .send_message
-        wm group .send_message .
-        wm title .send_message [_ "Send Message..."]
-        wm resizable .send_message 0 0
-        ::pd_bindings::dialog_bindings .send_message "send_message"
-        frame .send_message.frame
-        label .send_message.label -text [_ "Message"] -width 30 -height 15
-        pack .send_message.label .send_message.frame -side top -expand yes -fill both
-    }
+    ::dialog_message::open_message_dialog $::focused_window
+}
+
+proc ::pd_menucommands::menu_find_dialog {} {
+    ::dialog_find::open_find_dialog $::focused_window
 }
 
-proc ::pd_menucommands::menu_font_dialog {mytoplevel} {
+proc ::pd_menucommands::menu_font_dialog {} {
     if {[winfo exists .font]} {
         raise .font
-    } elseif {$mytoplevel eq ".pdwindow"} {
+    } elseif {$::focused_window eq ".pdwindow"} {
         pdtk_canvas_dofont .pdwindow [lindex [.pdwindow.text cget -font] 1]
     } else {
-        pdsend "$mytoplevel menufont"
+        pdsend "$::focused_window menufont"
     }
 }
 
@@ -110,20 +135,27 @@ proc ::pd_menucommands::menu_startup_dialog {} {
     }
 }
 
+proc ::pd_menucommands::menu_helpbrowser {} {
+    ::helpbrowser::open_helpbrowser
+}
+
+proc ::pd_menucommands::menu_texteditor {} {
+    ::pdwindow::error "the text editor is not implemented"
+}
+
 # ------------------------------------------------------------------------------
 # window management functions
 
-proc ::pd_menucommands::menu_minimize {mytoplevel} {
-    wm iconify $mytoplevel
+proc ::pd_menucommands::menu_minimize {window} {
+    wm iconify [winfo toplevel $window]
 }
 
-proc ::pd_menucommands::menu_maximize {mytoplevel} {
-    wm state $mytoplevel zoomed
+proc ::pd_menucommands::menu_maximize {window} {
+    wm state [winfo toplevel $window] zoomed
 }
 
-proc menu_raise_pdwindow {} {
-    set top_window [lindex [wm stackorder .pdwindow] end]
-    if {.pdwindow eq $top_window} {
+proc ::pd_menucommands::menu_raise_pdwindow {} {
+    if {$::focused_window eq ".pdwindow" && [winfo viewable .pdwindow]} {
         lower .pdwindow
     } else {
         wm deiconify .pdwindow
@@ -131,62 +163,89 @@ proc menu_raise_pdwindow {} {
     }
 }
 
+# used for cycling thru windows of an app
+proc ::pd_menucommands::menu_raisepreviouswindow {} {
+    lower [lindex [wm stackorder .] end] [lindex [wm stackorder .] 0]
+    focus [lindex [wm stackorder .] end]
+}
+
+# used for cycling thru windows of an app the other direction
+proc ::pd_menucommands::menu_raisenextwindow {} {
+    set mytoplevel [lindex [wm stackorder .] 0]
+    raise $mytoplevel
+    focus $mytoplevel
+}
+
+# ------------------------------------------------------------------------------
+# Pd window functions
+proc menu_clear_console {} {
+    ::pdwindow::clear_console
+}
+
 # ------------------------------------------------------------------------------
 # manage the saving of the directories for the new commands
 
 # this gets the dir from the path of a window's title
-proc ::pd_menucommands::set_menu_new_dir {mytoplevel} {
-    variable menu_new_dir
-    variable menu_open_dir
+proc ::pd_menucommands::set_filenewdir {mytoplevel} {
     # TODO add Aqua specifics once g_canvas.c has [wm attributes -titlepath]
     if {$mytoplevel eq ".pdwindow"} {
-        # puts "set_menu_new_dir $mytoplevel"
-        set menu_new_dir $menu_open_dir
+        set ::filenewdir $::fileopendir
     } else {
-        regexp -- ".+ - (.+)" [wm title $mytoplevel] ignored menu_new_dir
+        regexp -- ".+ - (.+)" [wm title $mytoplevel] ignored ::filenewdir
     }
 }
 
-# ------------------------------------------------------------------------------
-# opening docs as menu items (like the Test Audio and MIDI patch and the manual)
-proc ::pd_menucommands::menu_doc_open {subdir basename} {
-    set dirname "$::sys_libdir/$subdir"
-    
-    switch -- [string tolower [file extension $basename]] {
-        ".txt"    {::pd_menucommands::menu_opentext "$dirname/$basename"
-        } ".c"    {::pd_menucommands::menu_opentext "$dirname/$basename"
-        } ".htm"  {::pd_menucommands::menu_openhtml "$dirname/$basename"
-        } ".html" {::pd_menucommands::menu_openhtml "$dirname/$basename"
-        } default {
-            pdsend "pd open [enquote_path $basename] [enquote_path $dirname]"
+# parse the textfile for the About Pd page
+proc ::pd_menucommands::menu_aboutpd {} {
+    set versionstring "Pd $::PD_MAJOR_VERSION.$::PD_MINOR_VERSION.$::PD_BUGFIX_VERSION$::PD_TEST_VERSION"
+    set filename "$::sys_libdir/doc/1.manual/1.introduction.txt"
+    if {[winfo exists .aboutpd]} {
+        wm deiconify .aboutpd
+        raise .aboutpd
+    } else {
+        toplevel .aboutpd -class TextWindow
+        wm title .aboutpd [_ "About Pd"]
+        wm group .aboutpd .
+        .aboutpd configure -menu $::dialog_menubar
+        text .aboutpd.text -relief flat -borderwidth 0 \
+            -yscrollcommand ".aboutpd.scroll set" -background white
+        scrollbar .aboutpd.scroll -command ".aboutpd.text yview"
+        pack .aboutpd.scroll -side right -fill y
+        pack .aboutpd.text -side left -fill both -expand 1
+        bind .aboutpd <$::modifier-Key-w>   "wm withdraw .aboutpd"
+        
+        set textfile [open $filename]
+        while {![eof $textfile]} {
+            set bigstring [read $textfile 1000]
+            regsub -all PD_BASEDIR $bigstring $::sys_guidir bigstring2
+            regsub -all PD_VERSION $bigstring2 $versionstring bigstring3
+            .aboutpd.text insert end $bigstring3
         }
+        close $textfile
     }
 }
 
-# open text docs in a Pd window
-proc ::pd_menucommands::menu_opentext {filename} {
-    global pd_myversion
-    set mytoplevel [format ".help%d" [clock seconds]]
-    toplevel $mytoplevel -class TextWindow
-    text $mytoplevel.text -relief flat -borderwidth 0 \
-        -yscrollcommand "$mytoplevel.scroll set" -background white
-    scrollbar $mytoplevel.scroll -command "$mytoplevel.text yview"
-    pack $mytoplevel.scroll -side right -fill y
-    pack $mytoplevel.text -side left -fill both -expand 1
-    ::pd_bindings::window_bindings $mytoplevel
-    
-    set textfile [open $filename]
-    while {![eof $textfile]} {
-        set bigstring [read $textfile 1000]
-        regsub -all PD_BASEDIR $bigstring $::sys_guidir bigstring2
-        regsub -all PD_VERSION $bigstring2 $pd_myversion bigstring3
-        $mytoplevel.text insert end $bigstring3
+# ------------------------------------------------------------------------------
+# opening docs as menu items (like the Test Audio and MIDI patch and the manual)
+proc ::pd_menucommands::menu_doc_open {dir basename} {
+    if {[file pathtype $dir] eq "relative"} {
+        set dirname "$::sys_libdir/$dir"
+    } else {
+        set dirname $dir
+    }
+    set textextension "[string tolower [file extension $basename]]"
+    if {[lsearch -exact [lindex $::filetypes 0 1]  $textextension] > -1} {
+        set fullpath [file normalize [file join $dirname $basename]]
+        set dirname [file dirname $fullpath]
+        set basename [file tail $fullpath]
+        pdsend "pd open [enquote_path $basename] [enquote_path $dirname]"
+    } else {
+        ::pd_menucommands::menu_openfile "$dirname/$basename"
     }
-    close $textfile
 }
 
 # open HTML docs from the menu using the OS-default HTML viewer
-proc ::pd_menucommands::menu_openhtml {filename} {
+proc ::pd_menucommands::menu_openfile {filename} {
     if {$::tcl_platform(os) eq "Darwin"} {
         exec sh -c [format "open '%s'" $filename]
     } elseif {$::tcl_platform(platform) eq "windows"} {
diff --git a/pd/tcl/pd_menus.tcl b/pd/tcl/pd_menus.tcl
index 99b6be94d..1d4862c19 100644
--- a/pd/tcl/pd_menus.tcl
+++ b/pd/tcl/pd_menus.tcl
@@ -4,33 +4,18 @@
 package provide pd_menus 0.1
 
 package require pd_menucommands
-package require Tk
-#package require tile
-## replace Tk widgets with Ttk widgets on 8.5
-#namespace import -force ttk::*
 
 # TODO figure out Undo/Redo/Cut/Copy/Paste state changes for menus
-# TODO figure out parent window/window list for Window menu
-# TODO what is the Tcl package constructor or init()?
-# TODO $::pd_menus::menubar or .menubar globally?
 
 # since there is one menubar that is used for all windows, the menu -commands
 # use {} quotes so that $::focused_window is interpreted when the menu item
 # is called, not when the command is mapped to the menu item.  This is the
 # opposite of the 'bind' commands in pd_bindings.tcl
     
-
-# ------------------------------------------------------------------------------
-# global variables
-
-# TODO this should properly be inside the pd_menus namespace, now it is global
-namespace import ::pd_menucommands::* 
-
 namespace eval ::pd_menus:: {
     variable accelerator
     variable menubar ".menubar"
-    variable current_toplevel ".pdwindow"
-    
+
     namespace export create_menubar
     namespace export configure_for_pdwindow
     namespace export configure_for_canvas
@@ -52,32 +37,33 @@ proc ::pd_menus::create_menubar {} {
     }
     menu $menubar
     set menulist "file edit put find media window help"
-    if { $::windowingsystem eq "aqua" } {create_apple_menu $menubar}
-    # FIXME why does the following (if uncommented) kill my menubar?
-    # if { $::windowingsystem eq "win32" } {create_system_menu $menubar}
     foreach mymenu $menulist {    
         menu $menubar.$mymenu
         $menubar add cascade -label [_ [string totitle $mymenu]] \
             -menu $menubar.$mymenu
-        [format build_%s_menu $mymenu] $menubar.$mymenu .
-        if {$::windowingsystem eq "win32"} {
-            # fix menu font size on Windows with tk scaling = 1
-            $menubar.$mymenu configure -font menufont
-        }
+        [format build_%s_menu $mymenu] $menubar.$mymenu
     }
+    if {$::windowingsystem eq "aqua"} {create_apple_menu $menubar}
+    if {$::windowingsystem eq "win32"} {create_system_menu $menubar}
+    . configure -menu $menubar
 }
 
 proc ::pd_menus::configure_for_pdwindow {} {
     variable menubar
     # these are meaningless for the Pd window, so disable them
-    set file_items_to_disable {"Save" "Save As..." "Print..." "Close"}
-    foreach menuitem $file_items_to_disable {
-        $menubar.file entryconfigure [_ $menuitem] -state disabled
-    }
-    set edit_items_to_disable {"Undo" "Redo" "Duplicate" "Tidy Up" "Edit Mode"}
-    foreach menuitem $edit_items_to_disable {
-        $menubar.edit entryconfigure [_ $menuitem] -state disabled
-    }
+    # File menu
+    $menubar.file entryconfigure [_ "Save"] -state disabled
+    $menubar.file entryconfigure [_ "Save As..."] -state normal
+    $menubar.file entryconfigure [_ "Print..."] -state disabled
+    $menubar.file entryconfigure [_ "Close"] -state disabled
+    # Edit menu
+    $menubar.edit entryconfigure [_ "Duplicate"] -state disabled
+    $menubar.edit entryconfigure [_ "Tidy Up"] -state disabled
+    $menubar.edit entryconfigure [_ "Edit Mode"] -state disabled
+    pdtk_canvas_editmode .pdwindow 0
+    # Undo/Redo change names, they need to have the asterisk (*) after
+    $menubar.edit entryconfigure 0 -state disabled -label [_ "Undo"]
+    $menubar.edit entryconfigure 1 -state disabled -label [_ "Redo"]
     # disable everything on the Put menu
     for {set i 0} {$i <= [$menubar.put index end]} {incr i} {
         # catch errors that happen when trying to disable separators
@@ -87,32 +73,45 @@ proc ::pd_menus::configure_for_pdwindow {} {
 
 proc ::pd_menus::configure_for_canvas {mytoplevel} {
     variable menubar
-    set file_items_to_disable {"Save" "Save As..." "Print..." "Close"}
-    foreach menuitem $file_items_to_disable {
-        $menubar.file entryconfigure [_ $menuitem] -state normal
-    }
-    set edit_items_to_disable {"Undo" "Redo" "Duplicate" "Tidy Up" "Edit Mode"}
-    foreach menuitem $edit_items_to_disable {
-        $menubar.edit entryconfigure [_ $menuitem] -state normal
-    }
+    # File menu
+    $menubar.file entryconfigure [_ "Save"] -state normal
+    $menubar.file entryconfigure [_ "Save As..."] -state normal
+    $menubar.file entryconfigure [_ "Print..."] -state normal
+    $menubar.file entryconfigure [_ "Close"] -state normal
+    # Edit menu
+    $menubar.edit entryconfigure [_ "Duplicate"] -state normal
+    $menubar.edit entryconfigure [_ "Tidy Up"] -state normal
+    $menubar.edit entryconfigure [_ "Edit Mode"] -state normal
+    pdtk_canvas_editmode $mytoplevel $::editmode($mytoplevel)
+    # Put menu
     for {set i 0} {$i <= [$menubar.put index end]} {incr i} {
         # catch errors that happen when trying to disable separators
-        catch {$menubar.put entryconfigure $i -state normal }
+        if {[$menubar.put type $i] ne "separator"} {
+            $menubar.put entryconfigure $i -state normal 
+        }
     }
-    # TODO set "Edit Mode" state using editmode($mytoplevel)
+    update_undo_on_menu $mytoplevel
 }
 
 proc ::pd_menus::configure_for_dialog {mytoplevel} {
     variable menubar
-    # these are meaningless for the dialog panels, so disable them
-    set file_items_to_disable {"Save" "Save As..." "Print..." "Close"}
-    foreach menuitem $file_items_to_disable {
-        $menubar.file entryconfigure [_ $menuitem] -state disabled
-    }
-    set edit_items_to_disable {"Undo" "Redo" "Duplicate" "Tidy Up" "Edit Mode"}
-    foreach menuitem $edit_items_to_disable {
-        $menubar.edit entryconfigure [_ $menuitem] -state disabled
+    # these are meaningless for the dialog panels, so disable them except for
+    # the ones that make senes in the Find dialog panel
+    # File menu
+    if {$mytoplevel ne ".find"} {
+        $menubar.file entryconfigure [_ "Save"] -state disabled
+        $menubar.file entryconfigure [_ "Save As..."] -state disabled
+        $menubar.file entryconfigure [_ "Print..."] -state disabled
     }
+    $menubar.file entryconfigure [_ "Close"] -state disabled
+    # Edit menu
+    $menubar.edit entryconfigure [_ "Duplicate"] -state disabled
+    $menubar.edit entryconfigure [_ "Tidy Up"] -state disabled
+    $menubar.edit entryconfigure [_ "Edit Mode"] -state disabled
+    pdtk_canvas_editmode $mytoplevel 0
+    # Undo/Redo change names, they need to have the asterisk (*) after
+    $menubar.edit entryconfigure 0 -state disabled -label [_ "Undo"]
+    $menubar.edit entryconfigure 1 -state disabled -label [_ "Redo"]
     # disable everything on the Put menu
     for {set i 0} {$i <= [$menubar.put index end]} {incr i} {
         # catch errors that happen when trying to disable separators
@@ -123,19 +122,24 @@ proc ::pd_menus::configure_for_dialog {mytoplevel} {
 
 # ------------------------------------------------------------------------------
 # menu building functions
-proc ::pd_menus::build_file_menu {mymenu mytoplevel} {
+proc ::pd_menus::build_file_menu {mymenu} {
+    # run the platform-specific build_file_menu_* procs first, and config them
     [format build_file_menu_%s $::windowingsystem] $mymenu
     $mymenu entryconfigure [_ "New"]        -command {menu_new}
     $mymenu entryconfigure [_ "Open"]       -command {menu_open}
-    $mymenu entryconfigure [_ "Save"]       -command {pdsend "$::focused_window menusave"}
-    $mymenu entryconfigure [_ "Save As..."] -command {pdsend "$::focused_window menusaveas"}
-    #$mymenu entryconfigure [_ "Revert*"]    -command {menu_revert $current_toplevel}
-    $mymenu entryconfigure [_ "Close"]      -command {pdsend "$::focused_window menuclose 0"}
-    $mymenu entryconfigure [_ "Message"]    -command {menu_message_dialog}
+    $mymenu entryconfigure [_ "Save"]       -command {menu_send $::focused_window menusave}
+    $mymenu entryconfigure [_ "Save As..."] -command {menu_send $::focused_window menusaveas}
+    #$mymenu entryconfigure [_ "Revert*"]    -command {menu_revert $::focused_window}
+    $mymenu entryconfigure [_ "Close"]      -command {menu_send_float $::focused_window menuclose 0}
+    $mymenu entryconfigure [_ "Message..."] -command {menu_message_dialog}
     $mymenu entryconfigure [_ "Print..."]   -command {menu_print $::focused_window}
+    # update recent files
+    if {[llength $::recentfiles_list] > 0} {
+        ::pd_menus::update_recentfiles_menu false
+    }
 }
 
-proc ::pd_menus::build_edit_menu {mymenu mytoplevel} {
+proc ::pd_menus::build_edit_menu {mymenu} {
     variable accelerator
     $mymenu add command -label [_ "Undo"]       -accelerator "$accelerator+Z" \
         -command {menu_undo $::focused_window}
@@ -143,216 +147,340 @@ proc ::pd_menus::build_edit_menu {mymenu mytoplevel} {
         -command {menu_redo $::focused_window}
     $mymenu add  separator
     $mymenu add command -label [_ "Cut"]        -accelerator "$accelerator+X" \
-        -command {pdsend "$::focused_window cut"}
+        -command {menu_send $::focused_window cut}
     $mymenu add command -label [_ "Copy"]       -accelerator "$accelerator+C" \
-        -command {pdsend "$::focused_window copy"}
+        -command {menu_send $::focused_window copy}
     $mymenu add command -label [_ "Paste"]      -accelerator "$accelerator+V" \
-        -command {pdsend "$::focused_window paste"}
+        -command {menu_send $::focused_window paste}
     $mymenu add command -label [_ "Duplicate"]  -accelerator "$accelerator+D" \
-        -command {pdsend "$::focused_window duplicate"}
+        -command {menu_send $::focused_window duplicate}
     $mymenu add command -label [_ "Select All"] -accelerator "$accelerator+A" \
-        -command {pdsend "$::focused_window selectall"}
+        -command {menu_send $::focused_window selectall}
     $mymenu add  separator
     if {$::windowingsystem eq "aqua"} {
-        $mymenu add command -label [_ "Text Editor"] \
-            -command {menu_texteditor $::focused_window}
+#        $mymenu add command -label [_ "Text Editor"] \
+#            -command {menu_texteditor}
         $mymenu add command -label [_ "Font"]  -accelerator "$accelerator+T" \
-            -command {menu_font_dialog $::focused_window}
+            -command {menu_font_dialog}
     } else {
-        $mymenu add command -label [_ "Text Editor"] -accelerator "$accelerator+T"\
-            -command {menu_texteditor $::focused_window}
+#        $mymenu add command -label [_ "Text Editor"] -accelerator "$accelerator+T"\
+#            -command {menu_texteditor}
         $mymenu add command -label [_ "Font"] \
-            -command {menu_font_dialog $::focused_window}
+            -command {menu_font_dialog}
     }
     $mymenu add command -label [_ "Tidy Up"] \
-        -command {pdsend "$::focused_window tidy"}
-    $mymenu add command -label [_ "Toggle Console"] -accelerator "Shift+$accelerator+R" \
-        -command {.controls.switches.console invoke}
-    $mymenu add command -label [_ "Clear Console"] -accelerator "Shift+$accelerator+L" \
-        -command {menu_clear_console}
+        -command {menu_send $::focused_window tidy}
+    $mymenu add command -label [_ "Clear Console"] \
+        -accelerator "Shift+$accelerator+L" -command {menu_clear_console}
     $mymenu add  separator
-    #TODO madness! how to do set the state of the check box without invoking the menu!
+    #TODO madness! how to set the state of the check box without invoking the menu!
     $mymenu add check -label [_ "Edit Mode"] -accelerator "$accelerator+E" \
-        -selectcolor grey85 \
-        -command {pdsend "$::focused_window editmode 0"}
-    #if { ! [catch {console hide}]} { 
-    # TODO set up menu item to show/hide the Tcl/Tk console, if it available
-    #}
-
-    if {$::windowingsystem ne "aqua"} {
-        $mymenu add  separator
-        $mymenu add command -label [_ "Preferences"] \
-            -command {menu_preferences_dialog}
-    }
+        -variable ::editmode_button \
+        -command {menu_editmode $::editmode_button}
 }
 
-proc ::pd_menus::build_put_menu {mymenu mytoplevel} {
+proc ::pd_menus::build_put_menu {mymenu} {
     variable accelerator
+    # The trailing 0 in menu_send_float basically means leave the object box
+    # sticking to the mouse cursor. The iemguis alway do that when created
+    # from the menu, as defined in canvas_iemguis()
     $mymenu add command -label [_ "Object"] -accelerator "$accelerator+1" \
-        -command {pdsend "$::focused_window obj 0"} 
+        -command {menu_send_float $::focused_window obj 0} 
     $mymenu add command -label [_ "Message"] -accelerator "$accelerator+2" \
-        -command {pdsend "$::focused_window msg 0"}
+        -command {menu_send_float $::focused_window msg 0}
     $mymenu add command -label [_ "Number"] -accelerator "$accelerator+3" \
-        -command {pdsend "$::focused_window floatatom  0"}
+        -command {menu_send_float $::focused_window floatatom 0}
     $mymenu add command -label [_ "Symbol"] -accelerator "$accelerator+4" \
-        -command {pdsend "$::focused_window symbolatom  0"}
+        -command {menu_send_float $::focused_window symbolatom 0}
     $mymenu add command -label [_ "Comment"] -accelerator "$accelerator+5" \
-        -command {pdsend "$::focused_window text  0"}
+        -command {menu_send_float $::focused_window text 0}
     $mymenu add  separator
     $mymenu add command -label [_ "Bang"]    -accelerator "Shift+$accelerator+B" \
-        -command {pdsend "$::focused_window bng  0"}
+        -command {menu_send $::focused_window bng}
     $mymenu add command -label [_ "Toggle"]  -accelerator "Shift+$accelerator+T" \
-        -command {pdsend "$::focused_window toggle  0"}
+        -command {menu_send $::focused_window toggle}
     $mymenu add command -label [_ "Number2"] -accelerator "Shift+$accelerator+N" \
-        -command {pdsend "$::focused_window numbox  0"}
+        -command {menu_send $::focused_window numbox}
     $mymenu add command -label [_ "Vslider"] -accelerator "Shift+$accelerator+V" \
-        -command {pdsend "$::focused_window vslider  0"}
+        -command {menu_send $::focused_window vslider}
     $mymenu add command -label [_ "Hslider"] -accelerator "Shift+$accelerator+H" \
-        -command {pdsend "$::focused_window hslider  0"}
+        -command {menu_send $::focused_window hslider}
     $mymenu add command -label [_ "Vradio"]  -accelerator "Shift+$accelerator+D" \
-        -command {pdsend "$::focused_window vradio  0"}
+        -command {menu_send $::focused_window vradio}
     $mymenu add command -label [_ "Hradio"]  -accelerator "Shift+$accelerator+I" \
-        -command {pdsend "$::focused_window hradio  0"}
+        -command {menu_send $::focused_window hradio}
     $mymenu add command -label [_ "VU Meter"] -accelerator "Shift+$accelerator+U"\
-        -command {pdsend "$::focused_window vumeter  0"}
+        -command {menu_send $::focused_window vumeter}
     $mymenu add command -label [_ "Canvas"]  -accelerator "Shift+$accelerator+C" \
-        -command {pdsend "$::focused_window mycnv  0"}
+        -command {menu_send $::focused_window mycnv}
     $mymenu add  separator
-    $mymenu add command -label [_ "Graph"] -command {pdsend "$::focused_window graph"} 
-    $mymenu add command -label [_ "Array"] -command {pdsend "$::focused_window menuarray"}
+    $mymenu add command -label [_ "Graph"] -command {menu_send $::focused_window graph}
+    $mymenu add command -label [_ "Array"] -command {menu_send $::focused_window menuarray}
 }
 
-proc ::pd_menus::build_find_menu {mymenu mytoplevel} {
+proc ::pd_menus::build_find_menu {mymenu} {
     variable accelerator
     $mymenu add command -label [_ "Find..."]    -accelerator "$accelerator+F" \
-        -command {::dialog_find::menu_find_dialog $::focused_window}
+        -command {menu_find_dialog}
     $mymenu add command -label [_ "Find Again"] -accelerator "$accelerator+G" \
-        -command {pdsend "$::focused_window findagain"}
+        -command {menu_send $::focused_window findagain}
     $mymenu add command -label [_ "Find Last Error"] \
-        -command {pdsend "$::focused_window finderror"} 
+        -command {pdsend {pd finderror}} 
 }
 
-proc ::pd_menus::build_media_menu {mymenu mytoplevel} {
+proc ::pd_menus::build_media_menu {mymenu} {
     variable accelerator
     $mymenu add radiobutton -label [_ "DSP On"] -accelerator "$accelerator+/" \
         -variable ::dsp -value 1 -command {pdsend "pd dsp 1"}
     $mymenu add radiobutton -label [_ "DSP Off"] -accelerator "$accelerator+." \
         -variable ::dsp -value 0 -command {pdsend "pd dsp 0"}
+
     $mymenu add  separator
+    $mymenu add command -label [_ "Test Audio and MIDI..."] \
+        -command {menu_doc_open doc/7.stuff/tools testtone.pd} 
+    $mymenu add command -label [_ "Load Meter"] \
+        -command {menu_doc_open doc/7.stuff/tools load-meter.pd} 
 
-    set audioapi_list_length [llength $::audioapi_list]
-    for {set x 0} {$x<$audioapi_list_length} {incr x} {
-        # pdtk_post "audio [lindex [lindex $::audioapi_list $x] 0]"
-        $mymenu add radiobutton -label [lindex [lindex $::audioapi_list $x] 0] \
+    set audio_apilist_length [llength $::audio_apilist]
+    if {$audio_apilist_length > 0} {$mymenu add separator}
+    for {set x 0} {$x<$audio_apilist_length} {incr x} {
+        $mymenu add radiobutton -label [lindex [lindex $::audio_apilist $x] 0] \
             -command {menu_audio 0} -variable ::pd_whichapi \
-            -value [lindex [lindex $::audioapi_list $x] 1]\
+            -value [lindex [lindex $::audio_apilist $x] 1]\
             -command {pdsend "pd audio-setapi $::pd_whichapi"}
     }
-    if {$audioapi_list_length > 0} {$mymenu add separator}
-
-    set midiapi_list_length [llength $::midiapi_list]
-    for {set x 0} {$x<$midiapi_list_length} {incr x} {
-        # pdtk_post "midi [lindex [lindex $::midiapi_list $x] 0]"
-        $mymenu add radiobutton -label [lindex [lindex $::midiapi_list $x] 0] \
+    
+    set midi_apilist_length [llength $::midi_apilist]
+    if {$midi_apilist_length > 0} {$mymenu add separator}
+    for {set x 0} {$x<$midi_apilist_length} {incr x} {
+        $mymenu add radiobutton -label [lindex [lindex $::midi_apilist $x] 0] \
             -command {menu_midi 0} -variable ::pd_whichmidiapi \
-            -value [lindex [lindex $::midiapi_list $x] 1]\
+            -value [lindex [lindex $::midi_apilist $x] 1]\
             -command {pdsend "pd midi-setapi $::pd_whichmidiapi"}
     }
-    if {$midiapi_list_length > 0} {$mymenu add separator}
-
     if {$::windowingsystem ne "aqua"} {
-        $mymenu add command -label [_ "Audio settings..."] \
-            -command {pdsend "pd audio-properties"}
-        $mymenu add command -label [_ "MIDI settings..."] \
-            -command {pdsend "pd midi-properties"} 
         $mymenu add  separator
+        create_preferences_menu $mymenu.preferences
+        $mymenu add cascade -label [_ "Preferences"] -menu $mymenu.preferences
     }
-    $mymenu add command -label [_ "Test Audio and MIDI..."] \
-        -command {menu_doc_open doc/7.stuff/tools testtone.pd} 
-    $mymenu add command -label [_ "Load Meter"] \
-        -command {menu_doc_open doc/7.stuff/tools load-meter.pd} 
 }
 
-proc ::pd_menus::build_window_menu {mymenu mytoplevel} {
+proc ::pd_menus::build_window_menu {mymenu} {
     variable accelerator
     if {$::windowingsystem eq "aqua"} {
-        $mymenu add command -label [_ "Minimize"] -command {menu_minimize .} \
-            -accelerator "$accelerator+M"
-        $mymenu add command -label [_ "Zoom"] -command {menu_zoom .}
+        $mymenu add command -label [_ "Minimize"] -accelerator "$accelerator+M"\
+            -command {menu_minimize $::focused_window}
+        $mymenu add command -label [_ "Zoom"] \
+            -command {menu_maximize $::focused_window}
         $mymenu add  separator
+        $mymenu add command -label [_ "Bring All to Front"] \
+            -command {menu_bringalltofront}
+    } else {
+		$mymenu add command -label [_ "Next Window"] \
+            -command {menu_raisenextwindow} \
+            -accelerator [_ "$accelerator+Page Down"]
+		$mymenu add command -label [_ "Previous Window"] \
+            -command {menu_raisepreviouswindow} \
+            -accelerator [_ "$accelerator+Page Up"]
     }
-    $mymenu add command -label [_ "Parent Window"] \
-        -command {pdsend "$::focused_window findparent"}
+    $mymenu add  separator
     $mymenu add command -label [_ "Pd window"] -command {menu_raise_pdwindow} \
         -accelerator "$accelerator+R"
+    $mymenu add command -label [_ "Parent Window"] \
+        -command {menu_send $::focused_window findparent}
     $mymenu add  separator
-    if {$::windowingsystem eq "aqua"} {
-        $mymenu add command -label [_ "Bring All to Front"] \
-            -command {menu_bringalltofront}
-        $mymenu add  separator
-    }
 }
 
-proc ::pd_menus::build_help_menu {mymenu mytoplevel} {
+proc ::pd_menus::build_help_menu {mymenu} {
     if {$::windowingsystem ne "aqua"} {
-        $mymenu add command -label [_ "About Pd"] \
-            -command {menu_doc_open doc/1.manual 1.introduction.txt} 
+        $mymenu add command -label [_ "About Pd"] -command {menu_aboutpd} 
     }
     $mymenu add command -label [_ "HTML Manual..."] \
         -command {menu_doc_open doc/1.manual index.htm}
     $mymenu add command -label [_ "Browser..."] \
-        -command {placeholder menu_helpbrowser \$help_top_directory} 
+        -command {menu_helpbrowser} 
+    $mymenu add  separator
+    $mymenu add command -label [_ "puredata.info"] \
+        -command {menu_openfile {http://puredata.info}} 
+    $mymenu add command -label [_ "Report a bug"] -command {menu_openfile \
+        {http://sourceforge.net/tracker/?func=add&group_id=55736&atid=478070}} 
+    $mymenu add  separator
+    $mymenu add command -label [_ "Tcl prompt"] -command \
+        {::pdwindow::create_tcl_entry} 
+
+}
+
+#------------------------------------------------------------------------------#
+# undo/redo menu items
+
+proc ::pd_menus::update_undo_on_menu {mytoplevel} {
+    variable menubar
+    if {$mytoplevel eq $::undo_toplevel && $::undo_action ne "no"} {
+        $menubar.edit entryconfigure 0 -state normal \
+            -label [_ "Undo $::undo_action"]
+    } else {
+        $menubar.edit entryconfigure 0 -state disabled -label [_ "Undo"]
+    }
+    if {$mytoplevel eq $::undo_toplevel && $::redo_action ne "no"} {
+        $menubar.edit entryconfigure 1 -state normal \
+            -label [_ "Redo $::redo_action"]
+    } else {
+        $menubar.edit entryconfigure 1 -state disabled -label [_ "Redo"]
+    }
 }
 
 # ------------------------------------------------------------------------------
-# update the menu entries for opening recent files
-proc ::pd_menus::update_recentfiles_menu {} {
+# update the menu entries for opening recent files (write arg should always be true except the first time when pd is opened)
+proc ::pd_menus::update_recentfiles_menu {{write true}} {
     variable menubar
     switch -- $::windowingsystem {
-        "aqua" {::pd_menus::update_openrecent_menu_aqua .openrecent}
-        "win32" {update_recentfiles_on_menu $menubar.file}
-        "x11" {update_recentfiles_on_menu $menubar.file}
+        "aqua"  {::pd_menus::update_openrecent_menu_aqua .openrecent $write}
+        "win32" {::pd_menus::update_recentfiles_on_menu $menubar.file $write}
+        "x11"   {::pd_menus::update_recentfiles_on_menu $menubar.file $write}
     }
 }
 
 proc ::pd_menus::clear_recentfiles_menu {} {
     set ::recentfiles_list {}
     ::pd_menus::update_recentfiles_menu
+    # empty recentfiles in preferences (write empty array)
+    ::pd_guiprefs::write_recentfiles
 }
 
-proc ::pd_menus::update_openrecent_menu_aqua {mymenu} {
+proc ::pd_menus::update_openrecent_menu_aqua {mymenu {write}} {
     if {! [winfo exists $mymenu]} {menu $mymenu}
     $mymenu delete 0 end
+
+    # now the list is last first so we just add
     foreach filename $::recentfiles_list {
-        puts "creating menu item for $filename"
         $mymenu add command -label [file tail $filename] \
-            -command "open_file $filename"
+            -command "open_file {$filename}"
     }
+    # clear button
     $mymenu add  separator
     $mymenu add command -label [_ "Clear Menu"] \
         -command "::pd_menus::clear_recentfiles_menu"
+    # write to config file
+    if {$write == true} { ::pd_guiprefs::write_recentfiles }
 }
 
+# ------------------------------------------------------------------------------
 # this expects to be run on the File menu, and to insert above the last separator
-proc ::pd_menus::update_recentfiles_on_menu {mymenu} {
+proc ::pd_menus::update_recentfiles_on_menu {mymenu {write}} {
     set lastitem [$mymenu index end]
     set i 1
     while {[$mymenu type [expr $lastitem-$i]] ne "separator"} {incr i}
     set bottom_separator [expr $lastitem-$i]
     incr i
+
     while {[$mymenu type [expr $lastitem-$i]] ne "separator"} {incr i}
     set top_separator [expr $lastitem-$i]
     if {$top_separator < [expr $bottom_separator-1]} {
         $mymenu delete [expr $top_separator+1] [expr $bottom_separator-1]
     }
-    set i 0
-    foreach filename $::recentfiles_list {
-        $mymenu insert [expr $top_separator+$i+1] command \
-            -label [file tail $filename] -command "open_file $filename"
-        incr i
+    # insert the list from the end because we insert each element on the top
+    set i [llength $::recentfiles_list]
+    while {[incr i -1] > 0} {
+
+        set filename [lindex $::recentfiles_list $i]
+        $mymenu insert [expr $top_separator+1] command \
+            -label [file tail $filename] -command "open_file {$filename}"
+    }
+    set filename [lindex $::recentfiles_list 0]
+    $mymenu insert [expr $top_separator+1] command \
+        -label [file tail $filename] -command "open_file {$filename}"
+
+    # write to config file
+    if {$write == true} { ::pd_guiprefs::write_recentfiles }
+}
+
+# ------------------------------------------------------------------------------
+# lots of crazy recursion to update the Window menu
+
+# find the first parent patch that has a mapped window
+proc ::pd_menus::find_mapped_parent {parentlist} {
+    if {[llength $parentlist] == 0} {return "none"}
+    set firstparent [lindex $parentlist 0]
+    if {[winfo exists $firstparent]} {
+        return $firstparent
+    } elseif {[llength $parentlist] > 1} {
+        return [find_mapped_parent [lrange $parentlist 1 end]]
+    } else {
+        # we must be the first menu item to be inserted
+        return "none"
+    }
+}
+
+# find the first parent patch that has a mapped window
+proc ::pd_menus::insert_into_menu {mymenu entry parent} {
+    set insertat [$mymenu index end]
+    for {set i 0} {$i <= [$mymenu index end]} {incr i} {
+        if {[$mymenu type $i] ne "command"} {continue}
+        set currentcommand [$mymenu entrycget $i -command]
+        if {$currentcommand eq "raise $entry"} {return} ;# it exists already
+        if {$currentcommand eq "raise $parent"} {
+            set insertat $i
+        }
+    }
+    incr insertat
+    set label ""
+    for {set i 0} {$i < [llength $::parentwindows($entry)]} {incr i} {
+        append label " "
+    }
+    append label $::windowname($entry)
+    $mymenu insert $insertat command -label $label -command "raise $entry"
+}
+
+# recurse through a list of parent windows and add to the menu
+proc ::pd_menus::add_list_to_menu {mymenu window parentlist} {
+    if {[llength $parentlist] == 0} {
+        insert_into_menu $mymenu $window {}
+    } else {
+        set entry [lindex $parentlist end]
+        if {[winfo exists $entry]} {
+            insert_into_menu $mymenu $entry \
+                [find_mapped_parent $::parentwindows($entry)]
+        }
+    }
+    if {[llength $parentlist] > 1} {
+        add_list_to_menu $mymenu $window [lrange $parentlist 0 end-1]
+    }
+}
+
+# update the list of windows on the Window menu. This expects run on the
+# Window menu, and to insert below the last separator
+proc ::pd_menus::update_window_menu {} {
+    set mymenu $::patch_menubar.window
+    # find the last separator and delete everything after that
+    for {set i 0} {$i <= [$mymenu index end]} {incr i} {
+        if {[$mymenu type $i] eq "separator"} {
+            set deleteat $i
+        }
+    }
+    $mymenu delete $deleteat end
+    $mymenu add  separator
+    foreach window [array names ::parentwindows] {
+        set parentlist $::parentwindows($window)
+        add_list_to_menu $mymenu $window $parentlist
+        insert_into_menu $mymenu $window [find_mapped_parent $parentlist]
     }
 }
 
+# ------------------------------------------------------------------------------
+# submenu for Preferences, now used on all platforms
+
+proc ::pd_menus::create_preferences_menu {mymenu} {
+    menu $mymenu
+    $mymenu add command -label [_ "Path..."] \
+        -command {pdsend "pd start-path-dialog"}
+    $mymenu add command -label [_ "Startup..."] \
+        -command {pdsend "pd start-startup-dialog"}
+    $mymenu add command -label [_ "Audio Settings..."] \
+        -command {pdsend "pd audio-properties"}
+    $mymenu add command -label [_ "MIDI Settings..."] \
+        -command {pdsend "pd midi-properties"}
+}
+
 # ------------------------------------------------------------------------------
 # menu building functions for Mac OS X/aqua
 
@@ -360,23 +488,22 @@ proc ::pd_menus::update_recentfiles_on_menu {mymenu} {
 proc ::pd_menus::create_apple_menu {mymenu} {
     # TODO this should open a Pd patch called about.pd
     menu $mymenu.apple
-    $mymenu.apple add command -label [_ "About Pd"] \
-        -command {menu_doc_open doc/1.manual 1.introduction.txt} 
-    $mymenu add cascade -label "Apple" -menu $mymenu.apple
+    $mymenu.apple add command -label [_ "About Pd"] -command {menu_aboutpd}
     $mymenu.apple add  separator
-    # starting in 8.4.14, this is created automatically
-    set patchlevel [split [info patchlevel] .]
-    if {[lindex $patchlevel 1] < 5 && [lindex $patchlevel 2] < 14} {
-        $mymenu.apple add command -label [_ "Preferences..."] \
-            -command {menu_preferences_dialog" -accelerator "Cmd+,}
-    }
+    create_preferences_menu $mymenu.apple.preferences
+    $mymenu.apple add cascade -label [_ "Preferences"] \
+        -menu $mymenu.apple.preferences
+    # this needs to be last for things to function properly
+    $mymenu add cascade -label "Apple" -menu $mymenu.apple
+    
 }
 
 proc ::pd_menus::build_file_menu_aqua {mymenu} {
     variable accelerator
     $mymenu add command -label [_ "New"]       -accelerator "$accelerator+N"
     $mymenu add command -label [_ "Open"]      -accelerator "$accelerator+O"
-    ::pd_menus::update_openrecent_menu_aqua .openrecent
+    # this is now done in main ::pd_menus::build_file_menu
+    #::pd_menus::update_openrecent_menu_aqua .openrecent
     $mymenu add cascade -label [_ "Open Recent"] -menu .openrecent
     $mymenu add  separator
     $mymenu add command -label [_ "Close"]     -accelerator "$accelerator+W"
@@ -385,7 +512,7 @@ proc ::pd_menus::build_file_menu_aqua {mymenu} {
     #$mymenu add command -label [_ "Save All"]
     #$mymenu add command -label [_ "Revert to Saved"]
     $mymenu add  separator
-    $mymenu add command -label [_ "Message"]
+    $mymenu add command -label [_ "Message..."]
     $mymenu add  separator
     $mymenu add command -label [_ "Print..."]   -accelerator "$accelerator+P"
 }
@@ -412,7 +539,7 @@ proc ::pd_menus::build_file_menu_x11 {mymenu} {
     $mymenu add command -label [_ "Save As..."] -accelerator "Shift+$accelerator+S"
     #    $mymenu add command -label "Revert"
     $mymenu add  separator
-    $mymenu add command -label [_ "Message"]    -accelerator "$accelerator+M"
+    $mymenu add command -label [_ "Message..."]    -accelerator "$accelerator+M"
     $mymenu add command -label [_ "Print..."]   -accelerator "$accelerator+P"
     $mymenu add  separator
     # the recent files get inserted in here by update_recentfiles_on_menu
@@ -436,10 +563,16 @@ proc ::pd_menus::build_window_menu_x11 {mymenu} {
 # menu building functions for Windows/Win32
 
 # for Windows only
-proc ::pd_menus::create_system_menu {mymenu} {
-    $mymenu add cascade -menu [menu $mymenu.system]
+proc ::pd_menus::create_system_menu {mymenubar} {
+    set mymenu $mymenubar.system
+    $mymenubar add cascade -label System -menu $mymenu
+    menu $mymenu -tearoff 0
+    # placeholders
+    $mymenu add command -label [_ "Edit Mode"] -command "::pdwindow::verbose 0 systemmenu"
     # TODO add Close, Minimize, etc and whatever else is on the little menu
     # that is on the top left corner of the window frame
+    # http://wiki.tcl.tk/1006
+    # TODO add Edit Mode here
 }
 
 proc ::pd_menus::build_file_menu_win32 {mymenu} {
@@ -451,7 +584,9 @@ proc ::pd_menus::build_file_menu_win32 {mymenu} {
     $mymenu add command -label [_ "Save As..."] -accelerator "Shift+$accelerator+S"
     #    $mymenu add command -label "Revert"
     $mymenu add  separator
-    $mymenu add command -label [_ "Message"]  -accelerator "$accelerator+M"
+    $mymenu add command -label [_ "Message..."]  -accelerator "$accelerator+M"
+    create_preferences_menu $mymenu.preferences
+    $mymenu add cascade -label [_ "Preferences"] -menu $mymenu.preferences
     $mymenu add command -label [_ "Print..."] -accelerator "$accelerator+P"
     $mymenu add  separator
     # the recent files get inserted in here by update_recentfiles_on_menu
@@ -470,4 +605,3 @@ proc ::pd_menus::build_window_menu_win32 {mymenu} {
 }
 
 # the "Help" does not have cross-platform differences
-
diff --git a/pd/tcl/pd_menus.tcl~ b/pd/tcl/pd_menus.tcl~
deleted file mode 100644
index 94e6a9cd5..000000000
--- a/pd/tcl/pd_menus.tcl~
+++ /dev/null
@@ -1,473 +0,0 @@
-# Copyright (c) 1997-2009 Miller Puckette.
-#(c) 2008 WordTech Communications LLC. License: standard Tcl license, http://www.tcl.tk/software/tcltk/license.html
-
-package provide pd_menus 0.1
-
-package require pd_menucommands
-package require Tk
-#package require tile
-## replace Tk widgets with Ttk widgets on 8.5
-#namespace import -force ttk::*
-
-# TODO figure out Undo/Redo/Cut/Copy/Paste state changes for menus
-# TODO figure out parent window/window list for Window menu
-# TODO what is the Tcl package constructor or init()?
-# TODO $::pd_menus::menubar or .menubar globally?
-
-# since there is one menubar that is used for all windows, the menu -commands
-# use {} quotes so that $::focused_window is interpreted when the menu item
-# is called, not when the command is mapped to the menu item.  This is the
-# opposite of the 'bind' commands in pd_bindings.tcl
-    
-
-# ------------------------------------------------------------------------------
-# global variables
-
-# TODO this should properly be inside the pd_menus namespace, now it is global
-namespace import ::pd_menucommands::* 
-
-namespace eval ::pd_menus:: {
-    variable accelerator
-    variable menubar ".menubar"
-    variable current_toplevel ".pdwindow"
-    
-    namespace export create_menubar
-    namespace export configure_for_pdwindow
-    namespace export configure_for_canvas
-    namespace export configure_for_dialog
-
-    # turn off tearoff menus globally
-    option add *tearOff 0
-}
-
-# ------------------------------------------------------------------------------
-# 
-proc ::pd_menus::create_menubar {} {
-    variable accelerator
-    variable menubar
-    if {$::windowingsystem eq "aqua"} {
-        set accelerator "Cmd"
-    } else {
-        set accelerator "Ctrl"
-    }
-    menu $menubar
-    set menulist "file edit put find media window help"
-    if { $::windowingsystem eq "aqua" } {create_apple_menu $menubar}
-    # FIXME why does the following (if uncommented) kill my menubar?
-    # if { $::windowingsystem eq "win32" } {create_system_menu $menubar}
-    foreach mymenu $menulist {    
-        menu $menubar.$mymenu
-        $menubar add cascade -label [_ [string totitle $mymenu]] \
-            -menu $menubar.$mymenu
-        [format build_%s_menu $mymenu] $menubar.$mymenu .
-        if {$::windowingsystem eq "win32"} {
-            # fix menu font size on Windows with tk scaling = 1
-            $menubar.$mymenu configure -font menufont
-        }
-    }
-}
-
-proc ::pd_menus::configure_for_pdwindow {} {
-    variable menubar
-    # these are meaningless for the Pd window, so disable them
-    set file_items_to_disable {"Save" "Save As..." "Print..." "Close"}
-    foreach menuitem $file_items_to_disable {
-        $menubar.file entryconfigure [_ $menuitem] -state disabled
-    }
-    set edit_items_to_disable {"Undo" "Redo" "Duplicate" "Tidy Up" "Edit Mode"}
-    foreach menuitem $edit_items_to_disable {
-        $menubar.edit entryconfigure [_ $menuitem] -state disabled
-    }
-    # disable everything on the Put menu
-    for {set i 0} {$i <= [$menubar.put index end]} {incr i} {
-        # catch errors that happen when trying to disable separators
-        catch {$menubar.put entryconfigure $i -state disabled }
-    }
-}
-
-proc ::pd_menus::configure_for_canvas {mytoplevel} {
-    variable menubar
-    set file_items_to_disable {"Save" "Save As..." "Print..." "Close"}
-    foreach menuitem $file_items_to_disable {
-        $menubar.file entryconfigure [_ $menuitem] -state normal
-    }
-    set edit_items_to_disable {"Undo" "Redo" "Duplicate" "Tidy Up" "Edit Mode"}
-    foreach menuitem $edit_items_to_disable {
-        $menubar.edit entryconfigure [_ $menuitem] -state normal
-    }
-    for {set i 0} {$i <= [$menubar.put index end]} {incr i} {
-        # catch errors that happen when trying to disable separators
-        catch {$menubar.put entryconfigure $i -state normal }
-    }
-    # TODO set "Edit Mode" state using editmode($mytoplevel)
-}
-
-proc ::pd_menus::configure_for_dialog {mytoplevel} {
-    variable menubar
-    # these are meaningless for the dialog panels, so disable them
-    set file_items_to_disable {"Save" "Save As..." "Print..." "Close"}
-    foreach menuitem $file_items_to_disable {
-        $menubar.file entryconfigure [_ $menuitem] -state disabled
-    }
-    set edit_items_to_disable {"Undo" "Redo" "Duplicate" "Tidy Up" "Edit Mode"}
-    foreach menuitem $edit_items_to_disable {
-        $menubar.edit entryconfigure [_ $menuitem] -state disabled
-    }
-    # disable everything on the Put menu
-    for {set i 0} {$i <= [$menubar.put index end]} {incr i} {
-        # catch errors that happen when trying to disable separators
-        catch {$menubar.put entryconfigure $i -state disabled }
-    }
-}
-
-
-# ------------------------------------------------------------------------------
-# menu building functions
-proc ::pd_menus::build_file_menu {mymenu mytoplevel} {
-    [format build_file_menu_%s $::windowingsystem] $mymenu
-    $mymenu entryconfigure [_ "New"]        -command {menu_new}
-    $mymenu entryconfigure [_ "Open"]       -command {menu_open}
-    $mymenu entryconfigure [_ "Save"]       -command {pdsend "$::focused_window menusave"}
-    $mymenu entryconfigure [_ "Save As..."] -command {pdsend "$::focused_window menusaveas"}
-    #$mymenu entryconfigure [_ "Revert*"]    -command {menu_revert $current_toplevel}
-    $mymenu entryconfigure [_ "Close"]      -command {pdsend "$::focused_window menuclose 0"}
-    $mymenu entryconfigure [_ "Message"]    -command {menu_message_dialog}
-    $mymenu entryconfigure [_ "Print..."]   -command {menu_print $::focused_window}
-}
-
-proc ::pd_menus::build_edit_menu {mymenu mytoplevel} {
-    variable accelerator
-    $mymenu add command -label [_ "Undo"]       -accelerator "$accelerator+Z" \
-        -command {menu_undo $::focused_window}
-    $mymenu add command -label [_ "Redo"]       -accelerator "Shift+$accelerator+Z" \
-        -command {menu_redo $::focused_window}
-    $mymenu add  separator
-    $mymenu add command -label [_ "Cut"]        -accelerator "$accelerator+X" \
-        -command {pdsend "$::focused_window cut"}
-    $mymenu add command -label [_ "Copy"]       -accelerator "$accelerator+C" \
-        -command {pdsend "$::focused_window copy"}
-    $mymenu add command -label [_ "Paste"]      -accelerator "$accelerator+V" \
-        -command {pdsend "$::focused_window paste"}
-    $mymenu add command -label [_ "Duplicate"]  -accelerator "$accelerator+D" \
-        -command {pdsend "$::focused_window duplicate"}
-    $mymenu add command -label [_ "Select All"] -accelerator "$accelerator+A" \
-        -command {pdsend "$::focused_window selectall"}
-    $mymenu add  separator
-    if {$::windowingsystem eq "aqua"} {
-        $mymenu add command -label [_ "Text Editor"] \
-            -command {menu_texteditor $::focused_window}
-        $mymenu add command -label [_ "Font"]  -accelerator "$accelerator+T" \
-            -command {menu_font_dialog $::focused_window}
-    } else {
-        $mymenu add command -label [_ "Text Editor"] -accelerator "$accelerator+T"\
-            -command {menu_texteditor $::focused_window}
-        $mymenu add command -label [_ "Font"] \
-            -command {menu_font_dialog $::focused_window}
-    }
-    $mymenu add command -label [_ "Tidy Up"] \
-        -command {pdsend "$::focused_window tidy"}
-    $mymenu add command -label [_ "Toggle Console"] -accelerator "Shift+$accelerator+R" \
-        -command {.controls.switches.console invoke}
-    $mymenu add command -label [_ "Clear Console"] -accelerator "Shift+$accelerator+L" \
-        -command {menu_clear_console}
-    $mymenu add  separator
-	$mymenu add checkbutton -label [_ "Edit Mode"] \
-		-accelerator "$accelerator+E" -variable ::editmode_button \
-        -command {menu_editmode $::editmode_button}
-		-command {pdsend "$::focused_window editmode 0"}
-    #if { ! [catch {console hide}]} { 
-    # TODO set up menu item to show/hide the Tcl/Tk console, if it available
-    #}
-
-    if {$::windowingsystem ne "aqua"} {
-        $mymenu add  separator
-        $mymenu add command -label [_ "Preferences"] \
-            -command {menu_preferences_dialog}
-    }
-}
-
-proc ::pd_menus::build_put_menu {mymenu mytoplevel} {
-    variable accelerator
-    $mymenu add command -label [_ "Object"] -accelerator "$accelerator+1" \
-        -command {pdsend "$::focused_window obj 0"} 
-    $mymenu add command -label [_ "Message"] -accelerator "$accelerator+2" \
-        -command {pdsend "$::focused_window msg 0"}
-    $mymenu add command -label [_ "Number"] -accelerator "$accelerator+3" \
-        -command {pdsend "$::focused_window floatatom  0"}
-    $mymenu add command -label [_ "Symbol"] -accelerator "$accelerator+4" \
-        -command {pdsend "$::focused_window symbolatom  0"}
-    $mymenu add command -label [_ "Comment"] -accelerator "$accelerator+5" \
-        -command {pdsend "$::focused_window text  0"}
-    $mymenu add  separator
-    $mymenu add command -label [_ "Bang"]    -accelerator "Shift+$accelerator+B" \
-        -command {pdsend "$::focused_window bng  0"}
-    $mymenu add command -label [_ "Toggle"]  -accelerator "Shift+$accelerator+T" \
-        -command {pdsend "$::focused_window toggle  0"}
-    $mymenu add command -label [_ "Number2"] -accelerator "Shift+$accelerator+N" \
-        -command {pdsend "$::focused_window numbox  0"}
-    $mymenu add command -label [_ "Vslider"] -accelerator "Shift+$accelerator+V" \
-        -command {pdsend "$::focused_window vslider  0"}
-    $mymenu add command -label [_ "Hslider"] -accelerator "Shift+$accelerator+H" \
-        -command {pdsend "$::focused_window hslider  0"}
-    $mymenu add command -label [_ "Vradio"]  -accelerator "Shift+$accelerator+D" \
-        -command {pdsend "$::focused_window vradio  0"}
-    $mymenu add command -label [_ "Hradio"]  -accelerator "Shift+$accelerator+I" \
-        -command {pdsend "$::focused_window hradio  0"}
-    $mymenu add command -label [_ "VU Meter"] -accelerator "Shift+$accelerator+U"\
-        -command {pdsend "$::focused_window vumeter  0"}
-    $mymenu add command -label [_ "Canvas"]  -accelerator "Shift+$accelerator+C" \
-        -command {pdsend "$::focused_window mycnv  0"}
-    $mymenu add  separator
-    $mymenu add command -label [_ "Graph"] -command {pdsend "$::focused_window graph"} 
-    $mymenu add command -label [_ "Array"] -command {pdsend "$::focused_window menuarray"}
-}
-
-proc ::pd_menus::build_find_menu {mymenu mytoplevel} {
-    variable accelerator
-    $mymenu add command -label [_ "Find..."]    -accelerator "$accelerator+F" \
-        -command {::dialog_find::menu_find_dialog $::focused_window}
-    $mymenu add command -label [_ "Find Again"] -accelerator "$accelerator+G" \
-        -command {pdsend "$::focused_window findagain"}
-    $mymenu add command -label [_ "Find Last Error"] \
-        -command {pdsend "$::focused_window finderror"} 
-}
-
-proc ::pd_menus::build_media_menu {mymenu mytoplevel} {
-    variable accelerator
-    $mymenu add radiobutton -label [_ "DSP On"] -accelerator "$accelerator+/" \
-        -variable ::dsp -value 1 -command {pdsend "pd dsp 1"}
-    $mymenu add radiobutton -label [_ "DSP Off"] -accelerator "$accelerator+." \
-        -variable ::dsp -value 0 -command {pdsend "pd dsp 0"}
-    $mymenu add  separator
-
-    set audioapi_list_length [llength $::audioapi_list]
-    for {set x 0} {$x<$audioapi_list_length} {incr x} {
-        # pdtk_post "audio [lindex [lindex $::audioapi_list $x] 0]"
-        $mymenu add radiobutton -label [lindex [lindex $::audioapi_list $x] 0] \
-            -command {menu_audio 0} -variable ::pd_whichapi \
-            -value [lindex [lindex $::audioapi_list $x] 1]\
-            -command {pdsend "pd audio-setapi $::pd_whichapi"}
-    }
-    if {$audioapi_list_length > 0} {$mymenu add separator}
-
-    set midiapi_list_length [llength $::midiapi_list]
-    for {set x 0} {$x<$midiapi_list_length} {incr x} {
-        # pdtk_post "midi [lindex [lindex $::midiapi_list $x] 0]"
-        $mymenu add radiobutton -label [lindex [lindex $::midiapi_list $x] 0] \
-            -command {menu_midi 0} -variable ::pd_whichmidiapi \
-            -value [lindex [lindex $::midiapi_list $x] 1]\
-            -command {pdsend "pd midi-setapi $::pd_whichmidiapi"}
-    }
-    if {$midiapi_list_length > 0} {$mymenu add separator}
-
-    if {$::windowingsystem ne "aqua"} {
-        $mymenu add command -label [_ "Audio settings..."] \
-            -command {pdsend "pd audio-properties"}
-        $mymenu add command -label [_ "MIDI settings..."] \
-            -command {pdsend "pd midi-properties"} 
-        $mymenu add  separator
-    }
-    $mymenu add command -label [_ "Test Audio and MIDI..."] \
-        -command {menu_doc_open doc/7.stuff/tools testtone.pd} 
-    $mymenu add command -label [_ "Load Meter"] \
-        -command {menu_doc_open doc/7.stuff/tools load-meter.pd} 
-}
-
-proc ::pd_menus::build_window_menu {mymenu mytoplevel} {
-    variable accelerator
-    if {$::windowingsystem eq "aqua"} {
-        $mymenu add command -label [_ "Minimize"] -command {menu_minimize .} \
-            -accelerator "$accelerator+M"
-        $mymenu add command -label [_ "Zoom"] -command {menu_zoom .}
-        $mymenu add  separator
-    }
-    $mymenu add command -label [_ "Parent Window"] \
-        -command {pdsend "$::focused_window findparent"}
-    $mymenu add command -label [_ "Pd window"] -command {menu_raise_pdwindow} \
-        -accelerator "$accelerator+R"
-    $mymenu add  separator
-    if {$::windowingsystem eq "aqua"} {
-        $mymenu add command -label [_ "Bring All to Front"] \
-            -command {menu_bringalltofront}
-        $mymenu add  separator
-    }
-}
-
-proc ::pd_menus::build_help_menu {mymenu mytoplevel} {
-    if {$::windowingsystem ne "aqua"} {
-        $mymenu add command -label [_ "About Pd"] \
-            -command {menu_doc_open doc/1.manual 1.introduction.txt} 
-    }
-    $mymenu add command -label [_ "HTML Manual..."] \
-        -command {menu_doc_open doc/1.manual index.htm}
-    $mymenu add command -label [_ "Browser..."] \
-        -command {placeholder menu_helpbrowser \$help_top_directory} 
-}
-
-# ------------------------------------------------------------------------------
-# update the menu entries for opening recent files
-proc ::pd_menus::update_recentfiles_menu {} {
-    variable menubar
-    switch -- $::windowingsystem {
-        "aqua" {::pd_menus::update_openrecent_menu_aqua .openrecent}
-        "win32" {update_recentfiles_on_menu $menubar.file}
-        "x11" {update_recentfiles_on_menu $menubar.file}
-    }
-}
-
-proc ::pd_menus::clear_recentfiles_menu {} {
-    set ::recentfiles_list {}
-    ::pd_menus::update_recentfiles_menu
-}
-
-proc ::pd_menus::update_openrecent_menu_aqua {mymenu} {
-    if {! [winfo exists $mymenu]} {menu $mymenu}
-    $mymenu delete 0 end
-    foreach filename $::recentfiles_list {
-        puts "creating menu item for $filename"
-        $mymenu add command -label [file tail $filename] \
-            -command "open_file $filename"
-    }
-    $mymenu add  separator
-    $mymenu add command -label [_ "Clear Menu"] \
-        -command "::pd_menus::clear_recentfiles_menu"
-}
-
-# this expects to be run on the File menu, and to insert above the last separator
-proc ::pd_menus::update_recentfiles_on_menu {mymenu} {
-    set lastitem [$mymenu index end]
-    set i 1
-    while {[$mymenu type [expr $lastitem-$i]] ne "separator"} {incr i}
-    set bottom_separator [expr $lastitem-$i]
-    incr i
-    while {[$mymenu type [expr $lastitem-$i]] ne "separator"} {incr i}
-    set top_separator [expr $lastitem-$i]
-    if {$top_separator < [expr $bottom_separator-1]} {
-        $mymenu delete [expr $top_separator+1] [expr $bottom_separator-1]
-    }
-    set i 0
-    foreach filename $::recentfiles_list {
-        $mymenu insert [expr $top_separator+$i+1] command \
-            -label [file tail $filename] -command "open_file $filename"
-        incr i
-    }
-}
-
-# ------------------------------------------------------------------------------
-# menu building functions for Mac OS X/aqua
-
-# for Mac OS X only
-proc ::pd_menus::create_apple_menu {mymenu} {
-    # TODO this should open a Pd patch called about.pd
-    menu $mymenu.apple
-    $mymenu.apple add command -label [_ "About Pd"] \
-        -command {menu_doc_open doc/1.manual 1.introduction.txt} 
-    $mymenu add cascade -label "Apple" -menu $mymenu.apple
-    $mymenu.apple add  separator
-    # starting in 8.4.14, this is created automatically
-    set patchlevel [split [info patchlevel] .]
-    if {[lindex $patchlevel 1] < 5 && [lindex $patchlevel 2] < 14} {
-        $mymenu.apple add command -label [_ "Preferences..."] \
-            -command {menu_preferences_dialog" -accelerator "Cmd+,}
-    }
-}
-
-proc ::pd_menus::build_file_menu_aqua {mymenu} {
-    variable accelerator
-    $mymenu add command -label [_ "New"]       -accelerator "$accelerator+N"
-    $mymenu add command -label [_ "Open"]      -accelerator "$accelerator+O"
-    ::pd_menus::update_openrecent_menu_aqua .openrecent
-    $mymenu add cascade -label [_ "Open Recent"] -menu .openrecent
-    $mymenu add  separator
-    $mymenu add command -label [_ "Close"]     -accelerator "$accelerator+W"
-    $mymenu add command -label [_ "Save"]      -accelerator "$accelerator+S"
-    $mymenu add command -label [_ "Save As..."] -accelerator "$accelerator+Shift+S"
-    #$mymenu add command -label [_ "Save All"]
-    #$mymenu add command -label [_ "Revert to Saved"]
-    $mymenu add  separator
-    $mymenu add command -label [_ "Message"]
-    $mymenu add  separator
-    $mymenu add command -label [_ "Print..."]   -accelerator "$accelerator+P"
-}
-
-# the "Edit", "Put", and "Find" menus do not have cross-platform differences
-
-proc ::pd_menus::build_media_menu_aqua {mymenu} {
-}
-
-proc ::pd_menus::build_window_menu_aqua {mymenu} {
-}
-
-# the "Help" does not have cross-platform differences
- 
-# ------------------------------------------------------------------------------
-# menu building functions for UNIX/X11
-
-proc ::pd_menus::build_file_menu_x11 {mymenu} {
-    variable accelerator
-    $mymenu add command -label [_ "New"]        -accelerator "$accelerator+N"
-    $mymenu add command -label [_ "Open"]       -accelerator "$accelerator+O"
-    $mymenu add  separator
-    $mymenu add command -label [_ "Save"]       -accelerator "$accelerator+S"
-    $mymenu add command -label [_ "Save As..."] -accelerator "Shift+$accelerator+S"
-    #    $mymenu add command -label "Revert"
-    $mymenu add  separator
-    $mymenu add command -label [_ "Message"]    -accelerator "$accelerator+M"
-    $mymenu add command -label [_ "Print..."]   -accelerator "$accelerator+P"
-    $mymenu add  separator
-    # the recent files get inserted in here by update_recentfiles_on_menu
-    $mymenu add  separator
-    $mymenu add command -label [_ "Close"]      -accelerator "$accelerator+W"
-    $mymenu add command -label [_ "Quit"]       -accelerator "$accelerator+Q" \
-        -command {pdsend "pd verifyquit"}
-}
-
-# the "Edit", "Put", and "Find" menus do not have cross-platform differences
-
-proc ::pd_menus::build_media_menu_x11 {mymenu} {
-}
-
-proc ::pd_menus::build_window_menu_x11 {mymenu} {
-}
-
-# the "Help" does not have cross-platform differences
-
-# ------------------------------------------------------------------------------
-# menu building functions for Windows/Win32
-
-# for Windows only
-proc ::pd_menus::create_system_menu {mymenu} {
-    $mymenu add cascade -menu [menu $mymenu.system]
-    # TODO add Close, Minimize, etc and whatever else is on the little menu
-    # that is on the top left corner of the window frame
-}
-
-proc ::pd_menus::build_file_menu_win32 {mymenu} {
-    variable accelerator
-    $mymenu add command -label [_ "New"]      -accelerator "$accelerator+N"
-    $mymenu add command -label [_ "Open"]     -accelerator "$accelerator+O"
-    $mymenu add  separator
-    $mymenu add command -label [_ "Save"]      -accelerator "$accelerator+S"
-    $mymenu add command -label [_ "Save As..."] -accelerator "Shift+$accelerator+S"
-    #    $mymenu add command -label "Revert"
-    $mymenu add  separator
-    $mymenu add command -label [_ "Message"]  -accelerator "$accelerator+M"
-    $mymenu add command -label [_ "Print..."] -accelerator "$accelerator+P"
-    $mymenu add  separator
-    # the recent files get inserted in here by update_recentfiles_on_menu
-    $mymenu add  separator
-    $mymenu add command -label [_ "Close"]    -accelerator "$accelerator+W"
-    $mymenu add command -label [_ "Quit"]     -accelerator "$accelerator+Q"\
-        -command {pdsend "pd verifyquit"}
-}
-
-# the "Edit", "Put", and "Find" menus do not have cross-platform differences
-
-proc ::pd_menus::build_media_menu_win32 {mymenu} {
-}
-
-proc ::pd_menus::build_window_menu_win32 {mymenu} {
-}
-
-# the "Help" does not have cross-platform differences
-
diff --git a/pd/tcl/pdtk_canvas.tcl b/pd/tcl/pdtk_canvas.tcl
index 31505cec4..6db3f1b98 100644
--- a/pd/tcl/pdtk_canvas.tcl
+++ b/pd/tcl/pdtk_canvas.tcl
@@ -4,38 +4,122 @@ package provide pdtk_canvas 0.1
 package require pd_bindings
 
 namespace eval ::pdtk_canvas:: {
+    namespace export pdtk_canvas_popup
+    namespace export pdtk_canvas_editmode
+    namespace export pdtk_canvas_getscroll
+    namespace export pdtk_canvas_setparents
+    namespace export pdtk_canvas_reflecttitle
+    namespace export pdtk_canvas_menuclose
 }
 
+# One thing that is tricky to understand is the difference between a Tk
+# 'canvas' and a 'canvas' in terms of Pd's implementation.  They are similar,
+# but not the same thing.  In Pd code, a 'canvas' is basically a patch, while
+# the Tk 'canvas' is the backdrop for drawing everything that is in a patch.
+# The Tk 'canvas' is contained in a 'toplevel' window. That window has a Tk
+# class of 'PatchWindow'.
+
 # TODO figure out weird frameless window when you open a graph
 
+
+#TODO: http://wiki.tcl.tk/11502
+# MS Windows
+#wm geometry . returns contentswidthxcontentsheight+decorationTop+decorationLeftEdge.
+#and
+#winfo rooty . returns contentsTop
+#winfo rootx . returns contentsLeftEdge
+
+
+# this proc is split out on its own to make it easy to override. This makes it
+# easy for people to customize these calculations based on their Window
+# Manager, desires, etc.
+proc pdtk_canvas_place_window {width height geometry} {
+    set screenwidth [lindex [wm maxsize .] 0]
+    set screenheight [lindex [wm maxsize .] 1]
+
+    # read back the current geometry +posx+posy into variables
+    scan $geometry {%[+]%d%[+]%d} - x - y
+    # fit the geometry onto screen
+    set x [ expr $x % $screenwidth - $::windowframex]
+    set y [ expr $y % $screenheight - $::windowframey]
+    if {$x < 0} {set x 0}
+    if {$y < 0} {set y 0}
+    if {$width > $screenwidth} {
+        set width $screenwidth
+        set x 0
+    }
+    if {$height > $screenheight} {
+        set height [expr $screenheight - $::menubarsize - 30] ;# 30 for window framing
+        set y $::menubarsize
+    }
+    return [list $width $height ${width}x$height+$x+$y]
+}
+
+
 #------------------------------------------------------------------------------#
 # canvas new/saveas
 
 proc pdtk_canvas_new {mytoplevel width height geometry editable} {
-    # TODO check size of window
-    toplevel $mytoplevel -width $width -height $height -class CanvasWindow
+    set l [pdtk_canvas_place_window $width $height $geometry]
+    set width [lindex $l 0]
+    set height [lindex $l 1]
+    set geometry [lindex $l 2]
+
+    # release the window grab here so that the new window will
+    # properly get the Map and FocusIn events when its created
+    ::pdwindow::busyrelease
+    # set the loaded array for this new window so things can track state
+    set ::loaded($mytoplevel) 0
+    toplevel $mytoplevel -width $width -height $height -class PatchWindow
     wm group $mytoplevel .
-    $mytoplevel configure -menu .menubar
+    $mytoplevel configure -menu $::patch_menubar
+
+    # we have to wait until $mytoplevel exists before we can generate
+    # a <<Loading>> event for it, that's why this is here and not in the
+    # started_loading_file proc.  Perhaps this doesn't make sense tho
+    event generate $mytoplevel <<Loading>>
 
-    # TODO slide off screen windows into view
     wm geometry $mytoplevel $geometry
-    if {$::windowingsystem eq "aqua"} { # no menubar, it can be small
-        wm minsize $mytoplevel 50 20
-    } else { # leave room for the menubar
-        wm minsize $mytoplevel 310 30
+    wm minsize $mytoplevel $::canvas_minwidth $::canvas_minheight
+
+    set tkcanvas [tkcanvas_name $mytoplevel]
+    canvas $tkcanvas -width $width -height $height \
+        -highlightthickness 0 -scrollregion [list 0 0 $width $height] \
+        -xscrollcommand "$mytoplevel.xscroll set" \
+        -yscrollcommand "$mytoplevel.yscroll set"
+    scrollbar $mytoplevel.xscroll -orient horizontal -command "$tkcanvas xview"
+    scrollbar $mytoplevel.yscroll -orient vertical -command "$tkcanvas yview"
+    pack $tkcanvas -side left -expand 1 -fill both
+
+    # for some crazy reason, win32 mousewheel scrolling is in units of
+    # 120, and this forces Tk to interpret 120 to mean 1 scroll unit
+    if {$::windowingsystem eq "win32"} {
+        $tkcanvas configure -xscrollincrement 1 -yscrollincrement 1
     }
-    
-    set ::editmode($mytoplevel) $editable
 
-    set mycanvas $mytoplevel.c
-    canvas $mycanvas -width $width -height $height -background white \
-        -highlightthickness 0
-    # TODO add scrollbars here
-    pack $mycanvas -side left -expand 1 -fill both
+    ::pd_bindings::patch_bindings $mytoplevel
+
+    # give focus to the canvas so it gets the events rather than the window 	 
+    focus $tkcanvas
+
+    # let the scrollbar logic determine if it should make things scrollable
+    set ::xscrollable($tkcanvas) 0
+    set ::yscrollable($tkcanvas) 0
+
+    # init patch properties arrays
+    set ::editingtext($mytoplevel) 0
+    set ::childwindows($mytoplevel) {}
 
-    ::pd_bindings::canvas_bindings $mytoplevel
+    # this should be at the end so that the window and canvas are all ready
+    # before this variable changes.
+    set ::editmode($mytoplevel) $editable
+}
 
-    # give focus to the canvas so it gets the events rather than the window
+# if the patch canvas window already exists, then make it come to the front
+proc pdtk_canvas_raise {mytoplevel} {
+    wm deiconify $mytoplevel
+    raise $mytoplevel
+    set mycanvas $mytoplevel.c
     focus $mycanvas
 }
 
@@ -61,37 +145,71 @@ proc pdtk_canvas_saveas {name initialfile initialdir} {
     set dirname [file dirname $filename]
     set basename [file tail $filename]
     pdsend "$name savetofile [enquote_path $basename] [enquote_path $dirname]"
-    set ::pd_menucommands::menu_new_dir $dirname
+    set ::filenewdir $dirname
+    # add to recentfiles
+    ::pd_guiprefs::update_recentfiles $filename
+}
+
+##### ask user Save? Discard? Cancel?, and if so, send a message on to Pd ######
+proc ::pdtk_canvas::pdtk_canvas_menuclose {mytoplevel reply_to_pd} {
+    raise $mytoplevel
+    set filename [wm title $mytoplevel]
+    set message [format {Do you want to save the changes you made in "%s"?} $filename]
+    set answer [tk_messageBox -message $message -type yesnocancel -default "yes" \
+                    -parent $mytoplevel -icon question]
+    switch -- $answer {
+        yes { 
+            pdsend "$mytoplevel menusave"
+            if {[regexp {Untitled-[0-9]+} $filename]} {
+                # wait until pdtk_canvas_saveas finishes and writes to
+                # this var, otherwise the close command will be sent
+                # immediately and the file won't get saved
+                vwait ::filenewdir
+            }
+            pdsend $reply_to_pd
+        }
+        no {pdsend $reply_to_pd}
+        cancel {}
+    }
 }
 
 #------------------------------------------------------------------------------#
 # mouse usage
 
-proc pdtk_canvas_motion {mycanvas x y mods} {
-    set mytoplevel [winfo toplevel $mycanvas]
-    pdsend "$mytoplevel motion [$mycanvas canvasx $x] [$mycanvas canvasy $y] $mods"
+# TODO put these procs into the pdtk_canvas namespace
+proc pdtk_canvas_motion {tkcanvas x y mods} {
+    set mytoplevel [winfo toplevel $tkcanvas]
+    pdsend "$mytoplevel motion [$tkcanvas canvasx $x] [$tkcanvas canvasy $y] $mods"
 }
 
-proc pdtk_canvas_mouse {mycanvas x y b f} {
-    set mytoplevel [winfo toplevel $mycanvas]
-    pdsend "$mytoplevel mouse [$mycanvas canvasx $x] [$mycanvas canvasy $y] $b $f"
+proc pdtk_canvas_mouse {tkcanvas x y b f} {
+    set mytoplevel [winfo toplevel $tkcanvas]
+    pdsend "$mytoplevel mouse [$tkcanvas canvasx $x] [$tkcanvas canvasy $y] $b $f"
 }
 
-proc pdtk_canvas_mouseup {mycanvas x y b} {
-    set mytoplevel [winfo toplevel $mycanvas]
-    pdsend "$mytoplevel mouseup [$mycanvas canvasx $x] [$mycanvas canvasy $y] $b"
+proc pdtk_canvas_mouseup {tkcanvas x y b} {
+    set mytoplevel [winfo toplevel $tkcanvas]
+    pdsend "$mytoplevel mouseup [$tkcanvas canvasx $x] [$tkcanvas canvasy $y] $b"
 }
 
-proc pdtk_canvas_rightclick {mycanvas x y b} {
-    set mytoplevel [winfo toplevel $mycanvas]
-    pdsend "$mytoplevel mouse [$mycanvas canvasx $x] [$mycanvas canvasy $y] $b 8"
+proc pdtk_canvas_rightclick {tkcanvas x y b} {
+    set mytoplevel [winfo toplevel $tkcanvas]
+    pdsend "$mytoplevel mouse [$tkcanvas canvasx $x] [$tkcanvas canvasy $y] $b 8"
 }
 
 # on X11, button 2 pastes from X11 clipboard, so simulate normal paste actions
-proc pdtk_canvas_clickpaste {mycanvas x y b} {
-    pdtk_canvas_mouse $mycanvas $x $y $b 0
-    pdtk_canvas_mouseup $mycanvas $x $y $b
-    pdtk_pastetext
+proc pdtk_canvas_clickpaste {tkcanvas x y b} {
+    pdtk_canvas_mouse $tkcanvas $x $y $b 0
+    pdtk_canvas_mouseup $tkcanvas $x $y $b
+    if { [catch {set pdtk_pastebuffer [selection get]}] } {
+        # no selection... do nothing
+    } else {
+        for {set i 0} {$i < [string length $pdtk_pastebuffer]} {incr i 1} {
+            set cha [string index $pdtk_pastebuffer $i]
+            scan $cha %c keynum
+            pdsend "pd key 1 $keynum 0"
+        }
+    }
 }
 
 #------------------------------------------------------------------------------#
@@ -107,21 +225,21 @@ proc ::pdtk_canvas::create_popup {} {
         # the popup menu for the canvas
         menu .popup -tearoff false
         .popup add command -label [_ "Properties"] \
-            -command {popup_action $::focused_window 0}
+            -command {::pdtk_canvas::done_popup $::focused_window 0}
         .popup add command -label [_ "Open"]       \
-            -command {popup_action $::focused_window 1}
+            -command {::pdtk_canvas::done_popup $::focused_window 1}
         .popup add command -label [_ "Help"]       \
-            -command {popup_action $::focused_window 2}
+            -command {::pdtk_canvas::done_popup $::focused_window 2}
     }
 }
 
-proc popup_action {mytoplevel action} {
-    pdsend "$mytoplevel done-popup $action $::popup_xpix $::popup_ypix"
+proc ::pdtk_canvas::done_popup {mytoplevel action} {
+    pdsend "$mytoplevel done-popup $action $::popup_xcanvas $::popup_ycanvas"
 }
 
-proc pdtk_canvas_popup {mytoplevel xpix ypix hasproperties hasopen} {
-    set ::popup_xpix $xpix
-    set ::popup_ypix $ypix
+proc ::pdtk_canvas::pdtk_canvas_popup {mytoplevel xcanvas ycanvas hasproperties hasopen} {
+    set ::popup_xcanvas $xcanvas
+    set ::popup_ycanvas $ycanvas
     if {$hasproperties} {
         .popup entryconfigure [_ "Properties"] -state normal
     } else {
@@ -132,38 +250,137 @@ proc pdtk_canvas_popup {mytoplevel xpix ypix hasproperties hasopen} {
     } else {
         .popup entryconfigure [_ "Open"] -state disabled
     }
-    set mycanvas "$mytoplevel.c"
-    tk_popup .popup [expr $xpix + [winfo rootx $mycanvas]] \
-        [expr $ypix + [winfo rooty $mycanvas]] 0
+    set tkcanvas [tkcanvas_name $mytoplevel]
+    set scrollregion [$tkcanvas cget -scrollregion]
+    # get the canvas location that is currently the top left corner in the window
+    set left_xview_pix [expr [lindex [$tkcanvas xview] 0] * [lindex $scrollregion 2]]
+    set top_yview_pix [expr [lindex [$tkcanvas yview] 0] * [lindex $scrollregion 3]]
+    # take the mouse clicks in canvas coords, add the root of the canvas
+    # window, and subtract the area that is obscured by scrolling
+    set xpopup [expr int($xcanvas + [winfo rootx $tkcanvas] - $left_xview_pix)]
+    set ypopup [expr int($ycanvas + [winfo rooty $tkcanvas] - $top_yview_pix)]
+    tk_popup .popup $xpopup $ypopup 0
 }
 
 
+#------------------------------------------------------------------------------#
+# procs for when file loading starts/finishes
+
+proc ::pdtk_canvas::started_loading_file {patchname} {
+    ::pdwindow::busygrab
+}
+
+# things to run when a patch is finished loading.  This is called when
+# the OS sends the "Map" event for this window.
+proc ::pdtk_canvas::finished_loading_file {mytoplevel} {
+    # ::pdwindow::busyrelease is in pdtk_canvas_new so that the grab
+    # is released before the new toplevel window gets created.
+    # Otherwise the grab blocks the new window from getting the
+    # FocusIn event on creation.
+
+    # set editmode to make sure the menu item is in the right state
+    pdtk_canvas_editmode $mytoplevel $::editmode($mytoplevel)
+    set ::loaded($mytoplevel) 1
+    # send the virtual events now that everything is loaded
+    event generate $mytoplevel <<Loaded>>
+}
+
 #------------------------------------------------------------------------------#
 # procs for canvas events
 
 # check or uncheck the "edit" menu item
-proc pdtk_canvas_editval {mytoplevel value} {
-    set ::editmode($mytoplevel) $value
-# TODO figure how to change Edit Mode/Interact Mode text and have menu
-# enabling and disabling working still in pd_menus.tcl
-#    if {$value == 0} {
-#        $::pd_menus::menubar.edit entryconfigure [_ "Interact Mode"] -label [_ "Edit Mode"]
-#    } else {
-#        $::pd_menus::menubar.edit entryconfigure [_ "Edit Mode"] -label [_ "Interact Mode"]
-#    }
-    #$mytoplevel.menubar.edit entryconfigure [_ "Edit Mode"] -indicatoron $value
-    # TODO make this work, probably with a proc in pd_menus, or maybe the menu
-    # item can track the editmode variable
-}
-
-proc pdtk_undomenu {args} {
-    # TODO make this work, probably with a proc in pd_menus    
-    puts "pdtk_undomenu $args"
-}
-
-proc pdtk_canvas_getscroll {mycanvas} {
-    # TODO make this work
-    # the C code still sends a .c canvas, so get the toplevel
-    set mytoplevel [winfo toplevel $mycanvas]
-    # puts stderr "pdtk_canvas_getscroll $mycanvas"
+proc ::pdtk_canvas::pdtk_canvas_editmode {mytoplevel state} {
+    set ::editmode_button $state
+    set ::editmode($mytoplevel) $state
+    event generate $mytoplevel <<EditMode>>
+}
+
+# message from Pd to update the currently available undo/redo action
+proc pdtk_undomenu {mytoplevel undoaction redoaction} {
+    set ::undo_toplevel $mytoplevel
+    set ::undo_action $undoaction
+    set ::redo_action $redoaction
+    if {$mytoplevel ne "nobody"} {
+        ::pd_menus::update_undo_on_menu $mytoplevel
+    }
+}
+
+# This proc configures the scrollbars whenever anything relevant has
+# been updated.  It should always receive a tkcanvas, which is then
+# used to generate the mytoplevel, needed to address the scrollbars.
+proc ::pdtk_canvas::pdtk_canvas_getscroll {tkcanvas} {
+    set mytoplevel [winfo toplevel $tkcanvas]    
+    set bbox [$tkcanvas bbox all]
+    if {$bbox eq "" || [llength $bbox] != 4} {return}
+    set xupperleft [lindex $bbox 0]
+    set yupperleft [lindex $bbox 1]
+    if {$xupperleft > 0} {set xupperleft 0}
+    if {$yupperleft > 0} {set yupperleft 0}
+    set scrollregion [concat $xupperleft $yupperleft [lindex $bbox 2] [lindex $bbox 3]]
+    $tkcanvas configure -scrollregion $scrollregion
+    # X scrollbar
+    if {[lindex [$tkcanvas xview] 0] == 0.0 && [lindex [$tkcanvas xview] 1] == 1.0} {
+        set ::xscrollable($tkcanvas) 0
+        pack forget $mytoplevel.xscroll
+    } else {
+        set ::xscrollable($tkcanvas) 1
+        pack $mytoplevel.xscroll -side bottom -fill x -before $tkcanvas
+    }
+    # Y scrollbar, it gets touchy at the limit, so say > 0.995
+    if {[lindex [$tkcanvas yview] 0] == 0.0 && [lindex [$tkcanvas yview] 1] > 0.995} {
+        set ::yscrollable($tkcanvas) 0
+        pack forget $mytoplevel.yscroll
+    } else {
+        set ::yscrollable($tkcanvas) 1
+        pack $mytoplevel.yscroll -side right -fill y -before $tkcanvas
+    }
+}
+
+proc ::pdtk_canvas::scroll {tkcanvas axis amount} {
+    if {$axis eq "x" && $::xscrollable($tkcanvas) == 1} {
+        $tkcanvas xview scroll [expr {- ($amount)}] units
+    }
+    if {$axis eq "y" && $::yscrollable($tkcanvas) == 1} {
+        $tkcanvas yview scroll [expr {- ($amount)}] units
+    }
+}
+
+#------------------------------------------------------------------------------#
+# get patch window child/parent relationships
+
+# add a child window ID to the list of children, if it isn't already there
+proc ::pdtk_canvas::addchild {mytoplevel child} {
+    # if either ::childwindows($mytoplevel) does not exist, or $child does not
+    # exist inside of the ::childwindows($mytoplevel list
+    if { [lsearch -exact [array names ::childwindows $mytoplevel]] == -1 \
+             || [lsearch -exact $::childwindows($mytoplevel) $child] == -1} {
+        set ::childwindows($mytoplevel) [lappend ::childwindows($mytoplevel) $child]
+    }
+}
+
+# receive a list of all my parent windows from 'pd'
+proc ::pdtk_canvas::pdtk_canvas_setparents {mytoplevel args} {
+    set ::parentwindows($mytoplevel) $args
+    foreach parent $args {
+        addchild $parent $mytoplevel
+    }
+}
+
+# receive information for setting the info the the title bar of the window
+proc ::pdtk_canvas::pdtk_canvas_reflecttitle {mytoplevel \
+                                              path name arguments dirty} {
+    set ::windowname($mytoplevel) $name ;# TODO add path to this
+    if {$::windowingsystem eq "aqua"} {
+        wm attributes $mytoplevel -modified $dirty
+        if {[file exists "$path/$name"]} {
+            # for some reason -titlepath can still fail so just catch it 
+            if [catch {wm attributes $mytoplevel -titlepath "$path/$name"}] {
+                wm title $mytoplevel "$path/$name"
+            }
+        }
+        wm title $mytoplevel "$name$arguments"
+    } else {
+        if {$dirty} {set dirtychar "*"} else {set dirtychar " "}
+        wm title $mytoplevel "$name$dirtychar$arguments - $path" 
+    }
 }
diff --git a/pd/tcl/pdtk_text.tcl b/pd/tcl/pdtk_text.tcl
index bb37ccc3c..b23ae0b26 100644
--- a/pd/tcl/pdtk_text.tcl
+++ b/pd/tcl/pdtk_text.tcl
@@ -1,20 +1,56 @@
 
 package provide pdtk_text 0.1
 
-############ pdtk_text_new -- create a new text object #2###########
-proc pdtk_text_new {mycanvas canvasitem x y text font_size color} {
-    $mycanvas create text $x $y -tags $canvasitem -text $text -fill $color \
+# these procs are currently all in the global namespace because all of them
+# are used by 'pd' and therefore need to be in the global namespace.
+
+# create a new text object (ie. obj, msg, comment)
+proc pdtk_text_new {tkcanvas tags x y text font_size color} {
+    $tkcanvas create text $x $y -tags $tags -text $text -fill $color \
         -anchor nw -font [get_font_for_size $font_size]
-    $mycanvas bind $canvasitem <Home> "$mycanvas icursor $canvasitem 0"
-    $mycanvas bind $canvasitem <End> "$mycanvas icursor $canvasitem end"
+    set mytag [lindex $tags 0]
+    $tkcanvas bind $mytag <Home> "$tkcanvas icursor $mytag 0"
+    $tkcanvas bind $mytag <End>  "$tkcanvas icursor $mytag end"
+    # select all
+    $tkcanvas bind $mytag <Triple-ButtonRelease-1>  \
+        "pdtk_text_selectall $tkcanvas $mytag"
     if {$::windowingsystem eq "aqua"} { # emacs bindings for Mac OS X
-        $mycanvas bind $canvasitem <Control-a> "$mycanvas icursor $canvasitem 0"
-        $mycanvas bind $canvasitem <Control-e> "$mycanvas icursor $canvasitem end"
+        $tkcanvas bind $mytag <Control-a> "$tkcanvas icursor $mytag 0"
+        $tkcanvas bind $mytag <Control-e> "$tkcanvas icursor $mytag end"
+    }
+}
+
+# change the text in an existing text box
+proc pdtk_text_set {tkcanvas tag text} {
+    $tkcanvas itemconfig $tag -text $text
+}
+
+# paste into an existing text box by literally "typing" the contents of the
+# clipboard, i.e. send the contents one character at a time via 'pd key'
+proc pdtk_pastetext {args} {
+    if { [catch {set pdtk_pastebuffer [clipboard get]}] } {
+        # no selection... do nothing
+    } else {
+        for {set i 0} {$i < [string length $pdtk_pastebuffer]} {incr i 1} {
+            set cha [string index $pdtk_pastebuffer $i]
+            scan $cha %c keynum
+            pdsend "pd key 1 $keynum 0"
+        }
     }
 }
 
-################ pdtk_text_set -- change the text ##################
-proc pdtk_text_set {mycanvas canvasitem text} {
-    $mycanvas itemconfig $canvasitem -text $text
+# select all of the text in an existing text box
+proc pdtk_text_selectall {tkcanvas mytag} {
+    if {$::editmode([winfo toplevel $tkcanvas])} {
+        $tkcanvas select from $mytag 0
+        $tkcanvas select to $mytag end
+    }
 }
 
+# de/activate a text box for editing based on $editing flag
+proc pdtk_text_editing {mytoplevel tag editing} {
+    set tkcanvas [tkcanvas_name $mytoplevel]
+    if {$editing == 0} {selection clear $tkcanvas}
+    $tkcanvas focus $tag
+    set ::editingtext($mytoplevel) $editing
+}
diff --git a/pd/tcl/pdtk_textwindow.tcl b/pd/tcl/pdtk_textwindow.tcl
new file mode 100644
index 000000000..4d7e28224
--- /dev/null
+++ b/pd/tcl/pdtk_textwindow.tcl
@@ -0,0 +1,103 @@
+# Copyright (c) 2002-2012 krzYszcz and others.
+# For information on usage and redistribution, and for a DISCLAIMER OF ALL
+# WARRANTIES, see the file, "LICENSE.txt," in this distribution.  */
+
+# pdtk_textwindow - a window containing scrollable text for "qlist" and
+# "textfile" objects - later the latter might get renamed just "text"
+
+# this is adapted from krzYszcz's code for coll in cyclone
+
+package provide pdtk_textwindow 0.1
+
+# these procs are currently all in the global namespace because they're
+# called from pd.
+
+proc pdtk_textwindow_open {name geometry title font} {
+ if {[winfo exists $name]} {
+        $name.text delete 1.0 end
+    } else {
+        toplevel $name
+        wm title $name $title
+        wm geometry $name $geometry
+        wm protocol $name WM_DELETE_WINDOW \
+            [concat pdtk_textwindow_close $name 1]
+        bind $name <<Modified>> "pdtk_textwindow_dodirty $name"
+        text $name.text -relief raised -bd 2 \
+            -font [get_font_for_size $font] \
+            -yscrollcommand "$name.scroll set" -background white
+        scrollbar $name.scroll -command "$name.text yview"
+        pack $name.scroll -side right -fill y
+        pack $name.text -side left -fill both -expand 1
+        bind $name.text <$::modifier-Key-s> "pdtk_textwindow_send $name"
+        bind $name.text <$::modifier-Key-w> "pdtk_textwindow_close $name 1"
+        focus $name.text
+    }
+}
+
+proc pdtk_textwindow_dodirty {name} {
+    if {[catch {$name.text edit modified} dirty]} {set dirty 1}
+    set title [wm title $name]
+    set dt [string equal -length 1 $title "*"]
+    if {$dirty} {
+        if {$dt == 0} {wm title $name *$title}
+    } else {
+        if {$dt} {wm title $name [string range $title 1 end]}
+    }
+}
+
+proc pdtk_textwindow_setdirty {name flag} {
+    if {[winfo exists $name]} {
+        catch {$name.text edit modified $flag}
+    }
+}
+
+proc pdtk_textwindow_doclose {name} {
+    destroy $name
+    pdsend [concat $name signoff]
+}
+
+proc pdtk_textwindow_append {name contents} {
+    if {[winfo exists $name]} {
+        $name.text insert end $contents
+    }
+}
+
+proc pdtk_textwindow_clear {name} {
+    if {[winfo exists $name]} {
+        $name.text delete 1.0 end
+    }
+}
+
+proc pdtk_textwindow_send {name} {
+    if {[winfo exists $name]} {
+        pdsend [concat $name clear]
+        for {set i 1} \
+         {[$name.text compare $i.end < end]} \
+              {incr i 1} {
+            set lin [$name.text get $i.0 $i.end]
+            if {$lin != ""} {
+                regsub -all \; $lin " \\; " tmplin
+                regsub -all \, $tmplin " \\, " lin
+                pdsend [concat $name addline $lin]
+            }
+        }
+    }
+    pdtk_textwindow_setdirty $name 0
+}
+
+proc pdtk_textwindow_close {name ask} {
+    if {[winfo exists $name]} {
+        if {[catch {$name.text edit modified} dirty]} {set dirty 1}
+        if {$ask && $dirty} {
+            set title [wm title $name]
+            if {[string equal -length 1 $title "*"]} {
+                set title [string range $title 1 end]
+            }
+            set answer [tk_messageBox \-type yesnocancel \
+             \-icon question \
+             \-message [concat Save changes to \"$title\"?]]
+            if {$answer == "yes"} {pdtk_textwindow_send $name}
+            if {$answer != "cancel"} {pdsend [concat $name close]}
+        } else {pdsend [concat $name close]}
+    }
+}
diff --git a/pd/tcl/pdwindow.tcl b/pd/tcl/pdwindow.tcl
index d0c0c654b..7409bf317 100644
--- a/pd/tcl/pdwindow.tcl
+++ b/pd/tcl/pdwindow.tcl
@@ -2,52 +2,399 @@
 package provide pdwindow 0.1
 
 namespace eval ::pdwindow:: {
-    variable consolefont
-    variable printout_buffer ""
-    variable pdwindow_search_index
+    variable logbuffer {}
+    variable tclentry {}
+    variable tclentry_history {"console show"}
+    variable history_position 0
+    variable linecolor 0 ;# is toggled to alternate text line colors
+    variable logmenuitems
+    variable maxloglevel 4
 
+    variable lastlevel 0
+
+    namespace export create_window
     namespace export pdtk_post
+    namespace export pdtk_pd_dsp
+    namespace export pdtk_pd_dio
+}
+
+# TODO make the Pd window save its size and location between running
+
+proc ::pdwindow::set_layout {} {
+    variable maxloglevel
+    .pdwindow.text.internal tag configure log0 -foreground "#d00" -background "#ffe0e8"
+    .pdwindow.text.internal tag configure log1 -foreground "#d00"
+    # log2 messages are normal black on white
+    .pdwindow.text.internal tag configure log3 -foreground "#484848"
+
+    # 0-20(4-24) is a rough useful range of 'verbose' levels for impl debugging
+    set start 4
+    set end 25
+    for {set i $start} {$i < $end} {incr i} {
+        set B [expr int(($i - $start) * (40 / ($end - $start))) + 50]
+        .pdwindow.text.internal tag configure log${i} -foreground grey${B}
+    }
+}
+
+
+# grab focus on part of the Pd window when Pd is busy
+proc ::pdwindow::busygrab {} {
+    # set the mouse cursor to look busy and grab focus so it stays that way    
+    .pdwindow.text configure -cursor watch
+    grab set .pdwindow.text
+}
+
+# release focus on part of the Pd window when Pd is finished
+proc ::pdwindow::busyrelease {} {
+    .pdwindow.text configure -cursor xterm
+    grab release .pdwindow.text
 }
 
+# ------------------------------------------------------------------------------
+# pdtk functions for 'pd' to send data to the Pd window
 
+proc ::pdwindow::buffer_message {object_id level message} {
+    variable logbuffer
+    lappend logbuffer $object_id $level $message
+}
 
-proc ::pdwindow::pdtk_post {message} {
-    variable printout_buffer
-    # TODO this should be switchable between Pd window and stderr
-    if { ! [winfo exists .pdwindow.text]} {
-        set printout_buffer "$printout_buffer\n$message"
+proc ::pdwindow::insert_log_line {object_id level message} {
+    if {$object_id eq ""} {
+        .pdwindow.text.internal insert end $message log$level
     } else {
-        if {$printout_buffer ne ""} {
-            .pdwindow.text insert end "$printout_buffer\n"
-            set printout_buffer ""
+        .pdwindow.text.internal insert end $message [list log$level obj$object_id]
+        .pdwindow.text.internal tag bind obj$object_id <$::modifier-ButtonRelease-1> \
+            "::pdwindow::select_by_id $object_id; break"
+        .pdwindow.text.internal tag bind obj$object_id <Key-Return> \
+            "::pdwindow::select_by_id $object_id; break"
+        .pdwindow.text.internal tag bind obj$object_id <Key-KP_Enter> \
+            "::pdwindow::select_by_id $object_id; break"
+    }
+}
+
+# this has 'args' to satisfy trace, but its not used
+proc ::pdwindow::filter_buffer_to_text {args} {
+    variable logbuffer
+    variable maxloglevel
+    .pdwindow.text.internal delete 0.0 end
+    set i 0
+    foreach {object_id level message} $logbuffer {
+        if { $level <= $::loglevel || $maxloglevel == $::loglevel} {
+            insert_log_line $object_id $level $message
         }
-        .pdwindow.text insert end "$message\n"
-        .pdwindow.text yview end
+        # this could take a while, so update the GUI every 10000 lines
+        if { [expr $i % 10000] == 0} {update idletasks}
+        incr i
+    }
+    .pdwindow.text.internal yview end
+    ::pdwindow::verbose 10 "The Pd window filtered $i lines\n"
+}
+
+proc ::pdwindow::select_by_id {args} {
+    if [llength $args] { # Is $args empty?
+        pdsend "pd findinstance $args"
+    }
+}
+
+# logpost posts to Pd window with an object to trace back to and a
+# 'log level'. The logpost and related procs are for generating
+# messages that are useful for debugging patches.  They are messages
+# that are meant for the Pd programmer to see so that they can get
+# information about the patches they are building
+proc ::pdwindow::logpost {object_id level message} {
+    variable maxloglevel
+    variable lastlevel $level
+
+    buffer_message $object_id $level $message
+    if {[llength [info commands .pdwindow.text.internal]] && 
+        ($level <= $::loglevel || $maxloglevel == $::loglevel)} {
+        # cancel any pending move of the scrollbar, and schedule it
+        # after writing a line. This way the scrollbar is only moved once
+        # when the inserting has finished, greatly speeding things up
+        after cancel .pdwindow.text.internal yview end
+        insert_log_line $object_id $level $message
+        after idle .pdwindow.text.internal yview end
     }
-    puts stderr $message
+    # -stderr only sets $::stderr if 'pd-gui' is started before 'pd'
+    if {$::stderr} {puts stderr $message}
 }
 
+# shortcuts for posting to the Pd window
+proc ::pdwindow::fatal {message} {logpost {} 0 $message}
+proc ::pdwindow::error {message} {logpost {} 1 $message}
+proc ::pdwindow::post {message} {logpost {} 2 $message}
+proc ::pdwindow::debug {message} {logpost {} 3 $message}
+# for backwards compatibility
+proc ::pdwindow::bug {message} {logpost {} 3 $message}
+proc ::pdwindow::pdtk_post {message} {post $message}
+
+proc ::pdwindow::endpost {} {
+    variable linecolor
+    variable lastlevel
+    logpost {} $lastlevel "\n"
+    set linecolor [expr ! $linecolor]
+}
+
+# this verbose proc has a separate numbering scheme since its for
+# debugging implementations, and therefore falls outside of the 0-3
+# numbering on the Pd window.  They should only be shown in ALL mode.
+proc ::pdwindow::verbose {level message} {
+    incr level 4
+    logpost {} $level $message
+}
+
+# clear the log and the buffer
+proc ::pdwindow::clear_console {} {
+    variable logbuffer {}
+    .pdwindow.text.internal delete 0.0 end
+}
+
+# save the contents of the pdwindow::logbuffer to a file
+proc ::pdwindow::save_logbuffer_to_file {} {
+    variable logbuffer
+    set filename [tk_getSaveFile -initialfile "pdwindow.txt" -defaultextension .txt]
+    if {$filename eq ""} return; # they clicked cancel
+    set f [open $filename w]
+    puts $f "Pd $::PD_MAJOR_VERSION.$::PD_MINOR_VERSION.$::PD_BUGFIX_VERSION.$::PD_TEST_VERSION on $::windowingsystem"
+    puts $f "Tcl/Tk [info patchlevel]"
+    puts $f "------------------------------------------------------------------------------"
+    puts $f $logbuffer
+    close $f
+}
+
+
+#--compute audio/DSP checkbutton-----------------------------------------------#
+
+# set the checkbox on the "Compute Audio" menuitem and checkbox
+proc ::pdwindow::pdtk_pd_dsp {value} {
+    # TODO canvas_startdsp/stopdsp should really send 1 or 0, not "ON" or "OFF"
+    if {$value eq "ON"} {
+        set ::dsp 1
+    } else {
+        set ::dsp 0
+    }
+}
+
+proc ::pdwindow::pdtk_pd_dio {red} {
+    if {$red == 1} {
+        .pdwindow.header.dio configure -foreground red
+    } else {
+        .pdwindow.header.dio configure -foreground lightgray
+    }
+        
+}
+
+#--bindings specific to the Pd window------------------------------------------#
+
+proc ::pdwindow::pdwindow_bindings {} {
+    # these bindings are for the whole Pd window, minus the Tcl entry
+    foreach window {.pdwindow.text .pdwindow.header} {
+        bind $window <$::modifier-Key-x> "tk_textCut .pdwindow.text"
+        bind $window <$::modifier-Key-c> "tk_textCopy .pdwindow.text"
+        bind $window <$::modifier-Key-v> "tk_textPaste .pdwindow.text"
+    }
+    # Select All doesn't seem to work unless its applied to the whole window
+    bind .pdwindow <$::modifier-Key-a> ".pdwindow.text tag add sel 1.0 end"
+    # the "; break" part stops executing another binds, like from the Text class
+
+    # these don't do anything in the Pd window, so alert the user, then break
+    # so no more bindings run
+    bind .pdwindow <$::modifier-Key-s> "bell; break"
+    bind .pdwindow <$::modifier-Key-p> "bell; break"
+
+    # ways of hiding/closing the Pd window
+    if {$::windowingsystem eq "aqua"} {
+        # on Mac OS X, you can close the Pd window, since the menubar is there
+        bind .pdwindow <$::modifier-Key-w>   "wm withdraw .pdwindow"
+        wm protocol .pdwindow WM_DELETE_WINDOW "wm withdraw .pdwindow"
+    } else {
+        # TODO should it possible to close the Pd window and keep Pd open?
+        bind .pdwindow <$::modifier-Key-w>   "wm iconify .pdwindow"
+        wm protocol .pdwindow WM_DELETE_WINDOW "pdsend \"pd verifyquit\""
+    }
+}
+
+#--Tcl entry procs-------------------------------------------------------------#
+
+proc ::pdwindow::eval_tclentry {} {
+    variable tclentry
+    variable tclentry_history
+    variable history_position 0
+    if {$tclentry eq ""} {return} ;# no need to do anything if empty
+    if {[catch {uplevel #0 $tclentry} errorname]} {
+        global errorInfo
+        switch -regexp -- $errorname { 
+            "missing close-brace" {
+                ::pdwindow::error [concat [_ "(Tcl) MISSING CLOSE-BRACE '\}': "] $errorInfo]\n
+            } "missing close-bracket" {
+                ::pdwindow::error [concat [_ "(Tcl) MISSING CLOSE-BRACKET '\]': "] $errorInfo]\n
+            } "^invalid command name" {
+                ::pdwindow::error [concat [_ "(Tcl) INVALID COMMAND NAME: "] $errorInfo]\n
+            } default {
+                ::pdwindow::error [concat [_ "(Tcl) UNHANDLED ERROR: "] $errorInfo]\n
+            }
+        }
+    }
+    lappend tclentry_history $tclentry
+    set tclentry {}
+}
+
+proc ::pdwindow::get_history {direction} {
+    variable tclentry_history
+    variable history_position
+
+    incr history_position $direction
+    if {$history_position < 0} {set history_position 0}
+    if {$history_position > [llength $tclentry_history]} {
+        set history_position [llength $tclentry_history]
+    }
+    .pdwindow.tcl.entry delete 0 end
+    .pdwindow.tcl.entry insert 0 \
+        [lindex $tclentry_history end-[expr $history_position - 1]]
+}
+
+proc ::pdwindow::validate_tcl {} {
+    variable tclentry
+    if {[info complete $tclentry]} {
+        .pdwindow.tcl.entry configure -background "white"
+    } else {
+        .pdwindow.tcl.entry configure -background "#FFF0F0"
+    }
+}
+
+#--create tcl entry-----------------------------------------------------------#
+
+proc ::pdwindow::create_tcl_entry {} {
+# Tcl entry box frame
+    label .pdwindow.tcl.label -text [_ "Tcl:"] -anchor e
+    pack .pdwindow.tcl.label -side left
+    entry .pdwindow.tcl.entry -width 200 \
+       -exportselection 1 -insertwidth 2 -insertbackground blue \
+       -textvariable ::pdwindow::tclentry -font {$::font_family 12}
+    pack .pdwindow.tcl.entry -side left -fill x
+# bindings for the Tcl entry widget
+    bind .pdwindow.tcl.entry <$::modifier-Key-a> "%W selection range 0 end; break"
+    bind .pdwindow.tcl.entry <Return> "::pdwindow::eval_tclentry"
+    bind .pdwindow.tcl.entry <Up>     "::pdwindow::get_history 1"
+    bind .pdwindow.tcl.entry <Down>   "::pdwindow::get_history -1"
+    bind .pdwindow.tcl.entry <KeyRelease> +"::pdwindow::validate_tcl"
+
+    bind .pdwindow.text <Key-Tab> "focus .pdwindow.tcl.entry; break"
+}
+
+proc ::pdwindow::set_findinstance_cursor {widget key state} {
+    set triggerkeys [list Control_L Control_R Meta_L Meta_R]
+    if {[lsearch -exact $triggerkeys $key] > -1} {
+        if {$state == 0} {
+            $widget configure -cursor xterm
+        } else {
+            $widget configure -cursor based_arrow_up
+        }
+    }
+}
+
+#--create the window-----------------------------------------------------------#
+
 proc ::pdwindow::create_window {} {
-    variable consolefont
+    variable logmenuitems
+    set ::loaded(.pdwindow) 0
+
+    # colorize by class before creating anything
+    option add *PdWindow*Entry.highlightBackground "grey" startupFile
+    option add *PdWindow*Frame.background "grey" startupFile
+    option add *PdWindow*Label.background "grey" startupFile
+    option add *PdWindow*Checkbutton.background "grey" startupFile
+    option add *PdWindow*Menubutton.background "grey" startupFile
+    option add *PdWindow*Text.background "white" startupFile
+    option add *PdWindow*Entry.background "white" startupFile
+
     toplevel .pdwindow -class PdWindow
-    wm title .pdwindow [_ "Pd window"]
-    wm geometry .pdwindow =500x450+20+50
+    wm title .pdwindow [_ "Pd"]
+    set ::windowname(.pdwindow) [_ "Pd"]
+    if {$::windowingsystem eq "x11"} {
+        wm minsize .pdwindow 400 75
+    } else {
+        wm minsize .pdwindow 400 51
+    }
+    wm geometry .pdwindow =500x400+20+50
     .pdwindow configure -menu .menubar
-    ::pd_menus::configure_for_pdwindow
-    ::pd_bindings::pdwindow_bindings .pdwindow
 
-    frame .pdwindow.header
-    pack .pdwindow.header -side top -fill x -padx 30 -ipady 10
-    # label .pdwindow.header.label -text "The Pd window wants you to make it look nice!"
-    # pack .pdwindow.header.label -side left -fill y -anchor w
+    frame .pdwindow.header -borderwidth 1 -relief flat -background lightgray
+    pack .pdwindow.header -side top -fill x -ipady 5
+
+    frame .pdwindow.header.pad1
+    pack .pdwindow.header.pad1 -side left -padx 12
+
     checkbutton .pdwindow.header.dsp -text [_ "DSP"] -variable ::dsp \
-        -command "pdsend \"pd dsp 0\""
-    pack .pdwindow.header.dsp -side right -fill y -anchor e
+        -font {$::font_family 18 bold} -takefocus 1 -background lightgray \
+        -borderwidth 0  -command {pdsend "pd dsp $::dsp"}
+    pack .pdwindow.header.dsp -side right -fill y -anchor e -padx 5 -pady 0
+# DIO button
+    label .pdwindow.header.dio -text [_ "audio I/O error"] -borderwidth 0 \
+        -background lightgray -foreground lightgray \
+        -takefocus 0 \
+        -font {$::font_family 14}
+    pack .pdwindow.header.dio -side right -fill y -padx 30 -pady 0
+
+    label .pdwindow.header.loglabel -text [_ "Log:"] -anchor e \
+        -background lightgray
+    pack .pdwindow.header.loglabel -side left
+
+    set loglevels {0 1 2 3 4}
+    lappend logmenuitems "0 [_ fatal]"
+    lappend logmenuitems "1 [_ error]"
+    lappend logmenuitems "2 [_ normal]"
+    lappend logmenuitems "3 [_ debug]"
+    lappend logmenuitems "4 [_ all]"
+    set logmenu \
+        [eval tk_optionMenu .pdwindow.header.logmenu ::loglevel $loglevels]
+    .pdwindow.header.logmenu configure -background lightgray
+    foreach i $loglevels {
+        $logmenu entryconfigure $i -label [lindex $logmenuitems $i]
+    }
+    trace add variable ::loglevel write ::pdwindow::filter_buffer_to_text
+
+    # TODO figure out how to make the menu traversable with the keyboard
+    #.pdwindow.header.logmenu configure -takefocus 1
+    pack .pdwindow.header.logmenu -side left
+    frame .pdwindow.tcl -borderwidth 0
+    pack .pdwindow.tcl -side bottom -fill x
 # TODO this should use the pd_font_$size created in pd-gui.tcl    
     text .pdwindow.text -relief raised -bd 2 -font {-size 10} \
-        -yscrollcommand ".pdwindow.scroll set" -width 60
-    scrollbar .pdwindow.scroll -command ".pdwindow.text yview"
+        -highlightthickness 0 -borderwidth 1 -relief flat \
+        -yscrollcommand ".pdwindow.scroll set" -width 60 \
+        -undo false -autoseparators false -maxundo 1 -takefocus 0
+    scrollbar .pdwindow.scroll -command ".pdwindow.text.internal yview"
     pack .pdwindow.scroll -side right -fill y
-    pack .pdwindow.text -side bottom -fill both -expand 1
+    pack .pdwindow.text -side right -fill both -expand 1
     raise .pdwindow
+    focus .pdwindow.text
+    # run bindings last so that .pdwindow.tcl.entry exists
+    pdwindow_bindings
+    # set cursor to show when clicking in 'findinstance' mode
+    bind .pdwindow <KeyPress> "+::pdwindow::set_findinstance_cursor %W %K %s"
+    bind .pdwindow <KeyRelease> "+::pdwindow::set_findinstance_cursor %W %K %s"
+
+    # hack to make a good read-only text widget from http://wiki.tcl.tk/1152
+    rename ::.pdwindow.text ::.pdwindow.text.internal
+    proc ::.pdwindow.text {args} {
+        switch -exact -- [lindex $args 0] {
+            "insert" {}
+            "delete" {}
+            "default" { return [eval ::.pdwindow.text.internal $args] }
+        }
+    }
+    
+    # print whatever is in the queue after the event loop finishes
+    after idle [list after 0 ::pdwindow::filter_buffer_to_text]
+
+    set ::loaded(.pdwindow) 1
+
+    # set some layout variables
+    ::pdwindow::set_layout
+
+    # wait until .pdwindow.tcl.entry is visible before opening files so that
+    # the loading logic can grab it and put up the busy cursor
+    tkwait visibility .pdwindow.text
+#    create_tcl_entry
 }
diff --git a/pd/tcl/pkgIndex.tcl b/pd/tcl/pkgIndex.tcl
index cd28c6bd0..55322377d 100644
--- a/pd/tcl/pkgIndex.tcl
+++ b/pd/tcl/pkgIndex.tcl
@@ -14,15 +14,24 @@ package ifneeded pd_connect 0.1 [list source [file join $dir pd_connect.tcl]]
 package ifneeded dialog_array 0.1 [list source [file join $dir dialog_array.tcl]]
 package ifneeded dialog_audio 0.1 [list source [file join $dir dialog_audio.tcl]]
 package ifneeded dialog_canvas 0.1 [list source [file join $dir dialog_canvas.tcl]]
+package ifneeded dialog_data 0.1 [list source [file join $dir dialog_data.tcl]]
 package ifneeded dialog_find 0.1 [list source [file join $dir dialog_find.tcl]]
 package ifneeded dialog_font 0.1 [list source [file join $dir dialog_font.tcl]]
 package ifneeded dialog_gatom 0.1 [list source [file join $dir dialog_gatom.tcl]]
 package ifneeded dialog_iemgui 0.1 [list source [file join $dir dialog_iemgui.tcl]]
+package ifneeded dialog_message 0.1 [list source [file join $dir dialog_message.tcl]]
 package ifneeded dialog_midi 0.1 [list source [file join $dir dialog_midi.tcl]]
+package ifneeded dialog_path 0.1 [list source [file join $dir dialog_path.tcl]]
+package ifneeded dialog_startup 0.1 [list source [file join $dir dialog_startup.tcl]]
+package ifneeded helpbrowser 0.1 [list source [file join $dir helpbrowser.tcl]]
 package ifneeded opt_parser 0.1 [list source [file join $dir opt_parser.tcl]]
+package ifneeded pd_guiprefs 0.1 [list source [file join $dir pd_guiprefs.tcl]]
 package ifneeded pdwindow 0.1 [list source [file join $dir pdwindow.tcl]]
 package ifneeded pd_menucommands 0.1 [list source [file join $dir pd_menucommands.tcl]]
 package ifneeded pd_menus 0.1 [list source [file join $dir pd_menus.tcl]]
 package ifneeded pdtk_canvas 0.1 [list source [file join $dir pdtk_canvas.tcl]]
 package ifneeded pdtk_text 0.1 [list source [file join $dir pdtk_text.tcl]]
+package ifneeded pdtk_textwindow 0.1 [list source [file join $dir pdtk_textwindow.tcl]]
+package ifneeded scrollbox 0.1 [list source [file join $dir scrollbox.tcl]]
+package ifneeded scrollboxwindow 0.1 [list source [file join $dir scrollboxwindow.tcl]]
 package ifneeded wheredoesthisgo 0.1 [list source [file join $dir wheredoesthisgo.tcl]]
diff --git a/pd/tcl/scrollbox.tcl b/pd/tcl/scrollbox.tcl
new file mode 100644
index 000000000..b06670a0c
--- /dev/null
+++ b/pd/tcl/scrollbox.tcl
@@ -0,0 +1,191 @@
+######### scrollbox -- utility scrollbar with default bindings #######
+# scrollbox is used in the Path and Startup dialogs to edit lists of options
+
+package provide scrollbox 0.1
+
+namespace eval scrollbox {
+    # This variable keeps track of the last list element we clicked on,
+    # used to implement drag-drop reordering of list items
+    variable lastIdx 0
+}
+
+proc ::scrollbox::get_curidx { mytoplevel } {
+    set idx [$mytoplevel.listbox.box index active]
+    if {$idx < 0 || \
+            $idx == [$mytoplevel.listbox.box index end]} {
+        return [expr {[$mytoplevel.listbox.box index end] + 1}]
+    }
+    return [expr $idx]
+}
+
+proc ::scrollbox::insert_item { mytoplevel idx name } {
+    if {$name != ""} {
+        $mytoplevel.listbox.box insert $idx $name
+        set activeIdx [expr {[$mytoplevel.listbox.box index active] + 1}]
+        $mytoplevel.listbox.box see $activeIdx
+        $mytoplevel.listbox.box activate $activeIdx
+        $mytoplevel.listbox.box selection clear 0 end
+        $mytoplevel.listbox.box selection set active
+        focus $mytoplevel.listbox.box
+    }
+}
+
+proc ::scrollbox::add_item { mytoplevel add_method } {
+    set dir [$add_method]
+    insert_item $mytoplevel [expr {[get_curidx $mytoplevel] + 1}] $dir
+}
+
+proc ::scrollbox::edit_item { mytoplevel edit_method } {
+    set idx [expr {[get_curidx $mytoplevel]}]
+    set initialValue [$mytoplevel.listbox.box get $idx]
+    if {$initialValue != ""} {
+        set dir [$edit_method $initialValue]
+
+        if {$dir != ""} {
+            $mytoplevel.listbox.box delete $idx
+            insert_item $mytoplevel $idx $dir
+        }
+        $mytoplevel.listbox.box activate $idx
+        $mytoplevel.listbox.box selection clear 0 end
+        $mytoplevel.listbox.box selection set active
+        focus $mytoplevel.listbox.box
+    }
+}
+
+proc ::scrollbox::delete_item { mytoplevel } {
+    set cursel [$mytoplevel.listbox.box curselection]
+    foreach idx $cursel {
+        $mytoplevel.listbox.box delete $idx
+    }
+}
+
+# Double-clicking on the listbox should edit the current item,
+# or add a new one if there is no current
+proc ::scrollbox::dbl_click { mytoplevel edit_method add_method x y } {
+    if { $x == "" || $y == "" } {
+        return
+    }
+
+    set curBB [$mytoplevel.listbox.box bbox @$x,$y]
+
+    # listbox bbox returns an array of 4 items in the order:
+    # left, top, width, height
+    set height [lindex $curBB 3]
+    set top [lindex $curBB 1]
+    if { $height == "" || $top == "" } {
+        # If for some reason we didn't get valid bbox info,
+        # we want to default to adding a new item
+        set height 0
+        set top 0
+        set y 1
+    }
+
+    set bottom [expr {$height + $top}]
+
+    if {$y > $bottom} {
+        add_item $mytoplevel $add_method
+    } else {
+        edit_item $mytoplevel $edit_method
+    }
+}
+
+proc ::scrollbox::click { mytoplevel x y } {
+    # record the index of the current element being
+    # clicked on
+    variable ::lastIdx [$mytoplevel.listbox.box index @$x,$y]
+
+    focus $mytoplevel.listbox.box
+}
+
+# For drag-and-drop reordering, recall the last-clicked index
+# and move it to the position of the item currently under the mouse
+proc ::scrollbox::release { mytoplevel x y } {
+    variable lastIdx
+    set curIdx [$mytoplevel.listbox.box index @$x,$y]
+
+    if { $curIdx != $::lastIdx } {
+        # clear any current selection
+        $mytoplevel.listbox.box selection clear 0 end
+
+        set oldIdx $::lastIdx
+        set newIdx [expr {$curIdx+1}]
+        set selIdx $curIdx
+
+        if { $curIdx < $::lastIdx } {
+            set oldIdx [expr {$::lastIdx + 1}]
+            set newIdx $curIdx
+            set selIdx $newIdx
+        }
+
+        $mytoplevel.listbox.box insert $newIdx [$mytoplevel.listbox.box get $::lastIdx]
+        $mytoplevel.listbox.box delete $oldIdx
+        $mytoplevel.listbox.box activate $newIdx
+        $mytoplevel.listbox.box selection set $selIdx
+    }
+}
+
+# Make a scrollbox widget in a given window and set of data.
+#
+# id - the parent window for the scrollbox
+# listdata - array of data to populate the scrollbox
+# add_method - method to be called when we add a new item
+# edit_method - method to be called when we edit an existing item
+proc ::scrollbox::make { mytoplevel listdata add_method edit_method } {
+    frame $mytoplevel.listbox
+    listbox $mytoplevel.listbox.box \
+        -selectmode browse -activestyle dotbox \
+        -yscrollcommand [list "$mytoplevel.listbox.scrollbar" set]
+
+    # Create a scrollbar and keep it in sync with the current
+    # listbox view
+    pack $mytoplevel.listbox.box [scrollbar "$mytoplevel.listbox.scrollbar" \
+                              -command [list $mytoplevel.listbox.box yview]] \
+        -side left -fill y -anchor w 
+
+    # Populate the listbox widget
+    foreach item $listdata {
+        $mytoplevel.listbox.box insert end $item
+    }
+
+    # Standard listbox key/mouse bindings
+    event add <<Delete>> <Delete>
+    if { $::windowingsystem eq "aqua" } { event add <<Delete>> <BackSpace> }
+
+    bind $mytoplevel.listbox.box <ButtonPress> "::scrollbox::click $mytoplevel %x %y"
+    bind $mytoplevel.listbox.box <Double-1> "::scrollbox::dbl_click $mytoplevel $edit_method $add_method %x %y"
+    bind $mytoplevel.listbox.box <ButtonRelease> "::scrollbox::release $mytoplevel %x %y"
+    bind $mytoplevel.listbox.box <Return> "::scrollbox::edit_item $mytoplevel $edit_method"
+    bind $mytoplevel.listbox.box <<Delete>> "::scrollbox::delete_item $mytoplevel"
+
+    # <Configure> is called when the user modifies the window
+    # We use it to capture resize events, to make sure the
+    # currently selected item in the listbox is always visible
+    bind $mytoplevel <Configure> "$mytoplevel.listbox.box see active"
+
+    # The listbox should expand to fill its containing window
+    # the "-fill" option specifies which direction (x, y or both) to fill, while
+    # the "-expand" option (false by default) specifies whether the widget
+    # should fill
+    pack $mytoplevel.listbox.box -side left -fill both -expand 1
+    pack $mytoplevel.listbox -side top -pady 2m -padx 2m -fill both -expand 1
+
+    # All widget interactions can be performed without buttons, but
+    # we still need a "New..." button since the currently visible window
+    # might be full (even though the user can still expand it)
+    frame $mytoplevel.actions 
+    pack $mytoplevel.actions -side top -padx 2m -fill x 
+    button $mytoplevel.actions.add_path -text {New...} \
+        -command "::scrollbox::add_item $mytoplevel $add_method"
+    button $mytoplevel.actions.edit_path -text {Edit...} \
+        -command "::scrollbox::edit_item $mytoplevel $edit_method"
+    button $mytoplevel.actions.delete_path -text {Delete} \
+        -command "::scrollbox::delete_item $mytoplevel"
+
+    pack $mytoplevel.actions.delete_path -side right -pady 2m
+    pack $mytoplevel.actions.edit_path -side right -pady 2m
+    pack $mytoplevel.actions.add_path -side right -pady 2m
+
+    $mytoplevel.listbox.box activate end
+    $mytoplevel.listbox.box selection set end
+    focus $mytoplevel.listbox.box
+}
diff --git a/pd/tcl/scrollboxwindow.tcl b/pd/tcl/scrollboxwindow.tcl
new file mode 100644
index 000000000..d78622c66
--- /dev/null
+++ b/pd/tcl/scrollboxwindow.tcl
@@ -0,0 +1,94 @@
+
+####### scrollboxwindow -- scrollbox window with default bindings #########
+## This is the base dialog behind the Path and Startup dialogs
+## This namespace specifies everything the two dialogs have in common,
+## with arguments specifying the differences
+##
+## By default, this creates a dialog centered on the viewing area of the screen
+## with cancel, apply, and OK buttons
+## which contains a scrollbox widget populated with the given data
+
+package provide scrollboxwindow 0.1
+
+package require scrollbox
+
+namespace eval scrollboxwindow {
+}
+
+
+proc ::scrollboxwindow::get_listdata {mytoplevel} {
+    return [$mytoplevel.listbox.box get 0 end]
+}
+
+proc ::scrollboxwindow::do_apply {mytoplevel commit_method listdata} {
+    $commit_method [pdtk_encode $listdata]
+    pdsend "pd save-preferences"
+}
+
+# Cancel button action
+proc ::scrollboxwindow::cancel {mytoplevel} {
+    pdsend "$mytoplevel cancel"
+}
+
+# Apply button action
+proc ::scrollboxwindow::apply {mytoplevel commit_method } {
+    do_apply $mytoplevel $commit_method [get_listdata $mytoplevel]
+}
+
+# OK button action
+# The "commit" action can take a second or more,
+# long enough to be noticeable, so we only write
+# the changes after closing the dialog
+proc ::scrollboxwindow::ok {mytoplevel commit_method } {
+    set listdata [get_listdata $mytoplevel]
+    cancel $mytoplevel
+    do_apply $mytoplevel $commit_method $listdata
+}
+
+# "Constructor" function for building the window
+# id -- the window id to use
+# listdata -- the data used to populate the scrollbox
+# add_method -- a reference to a proc to be called when the user adds a new item
+# edit_method -- same as above, for editing and existing item
+# commit_method -- same as above, to commit during the "apply" action
+# title -- top-level title for the dialog
+# width, height -- initial width and height dimensions for the window, also minimum size
+proc ::scrollboxwindow::make {mytoplevel listdata add_method edit_method commit_method title width height } {
+    wm deiconify .pdwindow
+    raise .pdwindow
+    toplevel $mytoplevel -class DialogWindow
+    wm title $mytoplevel $title
+    wm group $mytoplevel .
+    wm transient $mytoplevel .pdwindow
+    wm protocol $mytoplevel WM_DELETE_WINDOW "::scrollboxwindow::cancel $mytoplevel"
+
+    # Enforce a minimum size for the window
+    wm minsize $mytoplevel $width $height
+
+    # Set the current dimensions of the window
+    wm geometry $mytoplevel "${width}x${height}"
+
+    # Add the scrollbox widget
+    ::scrollbox::make $mytoplevel $listdata $add_method $edit_method
+
+    # Use two frames for the buttons, since we want them both
+    # bottom and right
+    frame $mytoplevel.nb
+    pack $mytoplevel.nb -side bottom -fill x -pady 2m
+
+    frame $mytoplevel.nb.buttonframe
+    pack $mytoplevel.nb.buttonframe -side right -padx 2m
+
+    button $mytoplevel.nb.buttonframe.cancel -text [_ "Cancel"]\
+        -command "::scrollboxwindow::cancel $mytoplevel"
+    button $mytoplevel.nb.buttonframe.apply -text [_ "Apply"]\
+        -command "::scrollboxwindow::apply $mytoplevel $commit_method"
+    button $mytoplevel.nb.buttonframe.ok -text [_ "OK"]\
+        -command "::scrollboxwindow::ok $mytoplevel $commit_method"
+
+    pack $mytoplevel.nb.buttonframe.cancel -side left -expand 1 -padx 2m
+    pack $mytoplevel.nb.buttonframe.apply -side left -expand 1 -padx 2m
+    pack $mytoplevel.nb.buttonframe.ok -side left -expand 1 -padx 2m
+}
+
+
diff --git a/pd/tcl/wheredoesthisgo.tcl b/pd/tcl/wheredoesthisgo.tcl
index 3fbb9d1f5..acee40c07 100644
--- a/pd/tcl/wheredoesthisgo.tcl
+++ b/pd/tcl/wheredoesthisgo.tcl
@@ -3,48 +3,87 @@ package provide wheredoesthisgo 0.1
 
 # a place to temporarily store things until they find a home or go away
 
-set help_top_directory ""
+proc open_file {filename} {
+    set directory [file normalize [file dirname $filename]]
+    set basename [file tail $filename]
+    if {
+        [file exists $filename]
+        && [regexp -nocase -- "\.(pd|pat|mxt)$" $filename]
+    } then {
+        ::pdtk_canvas::started_loading_file [format "%s/%s" $basename $filename]
+        pdsend "pd open [enquote_path $basename] [enquote_path $directory]"
+        # now this is done in pd_guiprefs
+        ::pd_guiprefs::update_recentfiles $filename
+    } {
+        ::pdwindow::post [format [_ "Ignoring '%s': doesn't look like a Pd-file"] $filename]
+    }
+}
+    
+# ------------------------------------------------------------------------------
+# procs for panels (openpanel, savepanel)
 
+proc pdtk_openpanel {target localdir} {
+    if {! [file isdirectory $localdir]} {
+        if { ! [file isdirectory $::fileopendir]} {
+            set ::fileopendir $::env(HOME)
+        }
+        set localdir $::fileopendir
+    }
+    set filename [tk_getOpenFile -initialdir $localdir]
+    if {$filename ne ""} {
+        set ::fileopendir [file dirname $filename]
+        pdsend "$target callback [enquote_path $filename]"
+    }
+}
 
-proc post_tclinfo {} {
-    pdtk_post "Tcl library: [file normalize [info library]]"
-    pdtk_post "executable: [file normalize [info nameofexecutable]]"
-    pdtk_post "tclversion: [info tclversion]"
-    pdtk_post "patchlevel: [info patchlevel]"
-    pdtk_post "sharedlibextension: [info sharedlibextension]"
+proc pdtk_savepanel {target localdir} {
+    if {! [file isdirectory $localdir]} {
+        if { ! [file isdirectory $::filenewdir]} {
+            set ::filenewdir $::env(HOME)
+        }
+        set localdir $::filenewdir
+    }
+    set filename [tk_getSaveFile -initialdir $localdir]
+    if {$filename ne ""} {
+        pdsend "$target callback [enquote_path $filename]"
+    }
 }
 
+# ------------------------------------------------------------------------------
+# window info (name, path, parents, children, etc.)
 
-proc placeholder {args} {
-    # PLACEHOLDER
-    ::pdwindow::pdtk_post "PLACEHOLDER $args"
+proc lookup_windowname {mytoplevel} {
+    set window [array get ::windowname $mytoplevel]
+    if { $window ne ""} {
+        return [lindex $window 1]
+    } else {
+        return ERROR
+    }
 }
 
+proc tkcanvas_name {mytoplevel} {
+    return "$mytoplevel.c"
+}
 
-proc open_file {filename} {
-    set directory [file dirname $filename]
-    set basename [file tail $filename]
-    if {[regexp -nocase -- "\.(pd|pat|mxt)$" $filename]} {
-        pdsend "pd open [enquote_path $basename] [enquote_path $directory]"
-        # remove duplicates first, then the duplicate added after to the top
-        set index [lsearch -exact $::recentfiles_list $filename]
-        set ::recentfiles_list [lreplace $::recentfiles_list $index $index]
-        set ::recentfiles_list \
-            "$filename [lrange $::recentfiles_list 0 $::total_recentfiles]"
-        ::pd_menus::update_recentfiles_menu
-    }
+# ------------------------------------------------------------------------------
+# quoting functions
+
+# enquote a string for find, path, and startup dialog panels, to be decoded by
+# sys_decodedialog()
+proc pdtk_encodedialog {x} {
+    concat +[string map {" " "+_" "$" "+d" ";" "+s" "," "+c" "+" "++"} $x]
 }
 
-proc lookup_windowname {mytoplevel} {
-    foreach window $::menu_windowlist {
-        if {[lindex $window 1] eq $mytoplevel} {
-            return [lindex $window 0]
+# encode a list with pdtk_encodedialog
+proc pdtk_encode { listdata } {
+    set outlist {}
+    foreach this_path $listdata {
+        if {0==[string match "" $this_path]} {
+            lappend outlist [pdtk_encodedialog $this_path]
         }
     }
+    return $outlist
 }
-    
-# ------------------------------------------------------------------------------
-# quoting functions
 
 # TODO enquote a filename to send it to pd, " isn't handled properly tho...
 proc enquote_path {message} {
@@ -59,39 +98,14 @@ proc unspace_text {x} {
     concat $y
 }
 
-
 # ------------------------------------------------------------------------------
-# lost pdtk functions...
-
-# set the checkbox on the "Compute Audio" menuitem and checkbox
-proc pdtk_pd_dsp {value} {
-    # TODO canvas_startdsp/stopdsp should really send 1 or 0, not "ON" or "OFF"
-    if {$value eq "ON"} {
-        set ::dsp 1
-    } else {
-        set ::dsp 0
-    }
-}
-
-proc pdtk_pd_dio {red} {
-    # puts stderr [concat pdtk_pd_dio $red]
-}
-
+# watchdog functions
 
 proc pdtk_watchdog {} {
    pdsend "pd watchdog"
    after 2000 {pdtk_watchdog}
 }
 
-
 proc pdtk_ping {} {
     pdsend "pd ping"
 }
-
-# ------------------------------------------------------------------------------
-# kludges to avoid changing C code
-
-proc .mbar.find {command number} {
-    # this should be changed in g_canvas.c, around line 800
-    .menubar.find $command $number
-}
-- 
GitLab