diff --git a/extra/bonk~/bonk~-help.pd b/extra/bonk~/bonk~-help.pd
index d66ed5b0e9dbee2801bfaadb911844693312526c..e4bbb82782a452bbce162db994c6cea9a1701346 100644
--- a/extra/bonk~/bonk~-help.pd
+++ b/extra/bonk~/bonk~-help.pd
@@ -1,13 +1,13 @@
-#N canvas 304 143 967 599 10;
-#X obj 370 524 spigot;
+#N canvas 304 143 967 599 12;
+#X obj 358 544 spigot;
 #X msg 442 397 bang;
-#X obj 429 488 bonk~;
+#X obj 428 508 bonk~;
 #X msg 442 214 learn 1;
 #X msg 442 274 learn 0;
-#X msg 437 456 print;
+#X msg 446 451 print;
 #X obj 390 437 adc~;
-#X msg 614 538 \; pd dsp 1;
-#X obj 277 524 spigot;
+#X msg 613 558 \; pd dsp 1;
+#X obj 276 544 spigot;
 #N canvas 366 126 604 404 synth 0;
 #X obj 112 24 r bonk-cooked;
 #X obj 112 49 unpack;
@@ -59,14 +59,14 @@
 #X connect 21 0 1 0;
 #X connect 22 0 6 0;
 #X connect 23 0 9 0;
-#X restore 846 555 pd synth;
-#X floatatom 846 532 0 0 0 0 - - -;
-#X msg 846 502 0;
+#X restore 845 575 pd synth;
+#X floatatom 845 552 0 0 0 0 - - -;
+#X msg 845 522 0;
 #X msg 442 244 learn 10;
 #X msg 442 304 forget;
 #X msg 442 334 write templates.txt;
 #X msg 442 364 read templates.txt;
-#X msg 877 502 90;
+#X msg 876 522 90;
 #X msg 442 81 thresh 6 50;
 #X text 8 56 The Bonk object takes an audio signal input and looks
 for "attacks" defined as sharp changes in the spectral envelope of
@@ -75,13 +75,13 @@ Bonk check the attack against a collection of stored templates to try
 to guess which of two or more instruments was hit. Bonk is described
 theoretically in the 1998 ICMC proceedings \, reprinted on http://man104nfs.ucsd.edu/~mpuckett.
 ;
-#X text 602 504 click here;
-#X text 603 517 to start DSP;
+#X text 601 524 click here;
+#X text 602 537 to start DSP;
 #X text 8 377 In this patch \, after starting DSP \, you can print
 out the raw or cooked output using the two "spigots" or listen to a
 synthesizer output by raising its volume.;
-#X text 747 501 output volume;
-#X text 761 519 (0-100);
+#X text 746 521 output volume;
+#X text 760 539 (0-100);
 #X msg 442 150 mask 4 0.7;
 #X msg 442 184 debounce 0;
 #X text 8 309 Bonk's analysis is carried out on a 256-point window
@@ -89,7 +89,7 @@ synthesizer output by raising its volume.;
 The analysis period can be specified as Bonk's creation argument but
 must be a multiple of 64;
 #X text 532 185 Minimum time (msec) between attacks;
-#X text 532 140 Describes how energy in each frequency band masks later
+#X text 526 136 Describes how energy in each frequency band masks later
 energy in the band. Here the masking is total for 4 analysis periods
 and then drops by 0.7 each period.;
 #X text 530 214 Forget all templates and start learning new ones. The
@@ -101,22 +101,22 @@ to erase and record over a template.;
 #X text 595 334 Write templates to a file in text-editable format.
 ;
 #X text 596 364 Read templates from a file.;
-#X text 538 453 Print out all settings and templates.;
+#X text 514 450 Print out all settings and templates.;
 #X msg 442 120 minvel 10;
-#X text 538 392 Poll the current spectrum via "raw" outlet \, You can
+#X text 514 392 Poll the current spectrum via "raw" outlet \, You can
 set a very high threshold if you don't want attacks mixed in.;
 #X msg 437 426 debug 0;
-#X text 538 426 turn debugging on or off.;
-#X obj 326 525 tgl 15 0 empty empty empty 0 -6 0 8 -262144 -1 -1 0
+#X text 514 426 turn debugging on or off.;
+#X obj 325 545 tgl 15 0 empty empty empty 0 -6 0 8 -262144 -1 -1 0
 1;
-#X obj 419 525 tgl 15 0 empty empty empty 0 -6 0 8 -262144 -1 -1 0
+#X obj 407 545 tgl 15 0 empty empty empty 0 -6 0 8 -262144 -1 -1 0
 1;
-#X obj 370 554 print cooked;
-#X obj 277 554 print raw;
-#X text 253 490 enable printout:;
-#X text 533 121 Minimum "velocity" to output (quieter notes are ignored.)
+#X obj 358 574 print cooked;
+#X obj 276 574 print raw;
+#X text 158 545 enable printout:;
+#X text 530 121 Minimum "velocity" to output (quieter notes are ignored.)
 ;
-#X obj 462 513 s bonk-cooked;
+#X obj 461 533 s bonk-cooked;
 #X text 218 12 BONK~ - an attack detector for small percussion instruments
 ;
 #X text 8 174 Bonk's two outputs are the raw spectrum of the attack
@@ -127,12 +127,14 @@ an instrument number (counting up from zero) and a "velocity". This
 bands \, normalized so that 100 is an attack of amplitude of about
 1 The instrument number is significant only if Bonk has a "template
 set" in memory.;
-#X text 532 52 Set low and high thresholds. Signal growth must exceed
+#X text 531 47 Set low and high thresholds. Signal growth must exceed
 the high one and then fall to the low one to make an attack. The unit
 is the sum of the proportional growth in the 11 filter bands. Proportional
 growth is essentially the logarithmic time derivative.;
 #X text 238 27 (NOTE: this documentation does not yet describe new
 features for Pd 0.42).;
+#X msg 446 479 print 1;
+#X text 514 481 print out filterbank settings;
 #X connect 0 0 40 0;
 #X connect 1 0 2 0;
 #X connect 2 0 8 0;
@@ -157,3 +159,4 @@ features for Pd 0.42).;
 #X connect 36 0 2 0;
 #X connect 38 0 8 1;
 #X connect 39 0 0 1;
+#X connect 49 0 2 0;
diff --git a/extra/bonk~/bonk~.c b/extra/bonk~/bonk~.c
index ee59cb0f76ef6162028ac7c26225ff2ad10658d5..369d9e38ce4ed9044efdd471e3800ac918504a0e 100644
--- a/extra/bonk~/bonk~.c
+++ b/extra/bonk~/bonk~.c
@@ -95,6 +95,7 @@ static t_class *bonk_class;
 #define DEFHALFTONES 6
 #define DEFOVERLAP 1
 #define DEFFIRSTBIN 1
+#define DEFMINBANDWIDTH 1.5
 #define DEFHITHRESH 5
 #define DEFLOTHRESH 2.5
 #define DEFMASKTIME 4
@@ -122,6 +123,7 @@ typedef struct _filterbank
     float b_halftones;          /* filter bandwidth in halftones */
     float b_overlap;            /* overlap; default 1 for 1/2-power pts */
     float b_firstbin;           /* freq of first filter in bins, default 1 */
+    float b_minbandwidth;       /* minimum bandwidth, default 1.5 */
     t_filterkernel *b_vec;      /* filter kernels */
     int b_refcount;             /* number of bonk~ objects using this */
     struct _filterbank *b_next; /* next in linked list */
@@ -206,7 +208,7 @@ typedef struct _bonk
     float x_halftones;      /* nominal halftones between filters */
     float x_overlap;
     float x_firstbin;
-
+    float x_minbandwidth;
     float x_hithresh;       /* threshold for total growth to trigger */
     float x_lothresh;       /* threshold for total growth to re-arm */
     float x_minvel;         /* minimum velocity we output */
@@ -275,10 +277,11 @@ char *strcpy(char *s1, const char *s2);
 
 static void bonk_tick(t_bonk *x);
 
-#define HALFWIDTH 0.75 /* half peak bandwidth at half power point in bins */
+#define HALFWIDTH 0.75  /* half peak bandwidth at half power point in bins */
+#define SLIDE 0.25    /* relative slide between filter subwindows */
 
 static t_filterbank *bonk_newfilterbank(int npoints, int nfilters,
-    float halftones, float overlap, float firstbin)
+    float halftones, float overlap, float firstbin, float minbandwidth)
 {
     int i, j;
     float cf, bw, h, relspace;
@@ -288,6 +291,7 @@ static t_filterbank *bonk_newfilterbank(int npoints, int nfilters,
     b->b_halftones = halftones;
     b->b_overlap = overlap;
     b->b_firstbin = firstbin;
+    b->b_minbandwidth = minbandwidth;
     b->b_refcount = 0;
     b->b_next = bonk_filterbanklist;
     bonk_filterbanklist = b;
@@ -296,17 +300,21 @@ static t_filterbank *bonk_newfilterbank(int npoints, int nfilters,
     h = exp((log(2.)/12.)*halftones);  /* specced interval between filters */
     relspace = (h - 1)/(h + 1);        /* nominal spacing-per-f for fbank */
     
+    if (minbandwidth < 2*HALFWIDTH)
+        minbandwidth = 2*HALFWIDTH;
+    if (firstbin < minbandwidth/(2*HALFWIDTH))
+        firstbin = minbandwidth/(2*HALFWIDTH);
     cf = firstbin;
     bw = cf * relspace * overlap;
-    if (bw < HALFWIDTH)
-        bw = HALFWIDTH;
+    if (bw < (0.5*minbandwidth))
+        bw = (0.5*minbandwidth);
     for (i = 0; i < nfilters; i++)
     {
         float *fp, newcf, newbw;
         float normalizer = 0;
         int filterpoints, skippoints, hoppoints, nhops;
         
-        filterpoints = 0.5 + npoints * HALFWIDTH/bw;
+        filterpoints = npoints * HALFWIDTH/bw;
         if (cf > npoints/2)
         {
             post("bonk~: only using %d filters (ran past Nyquist)", i+1);
@@ -320,7 +328,7 @@ static t_filterbank *bonk_newfilterbank(int npoints, int nfilters,
         else if (filterpoints > npoints)
             filterpoints = npoints;
         
-        hoppoints = 0.5 + 0.5 * npoints * HALFWIDTH/bw;
+        hoppoints = SLIDE * npoints * HALFWIDTH/bw;
         
         nhops = 1. + (npoints-filterpoints)/(float)hoppoints;
         skippoints = 0.5 * (npoints-filterpoints - (nhops-1) * hoppoints);
@@ -343,7 +351,7 @@ static t_filterbank *bonk_newfilterbank(int npoints, int nfilters,
             fp[1] = window * sin(phase);
             normalizer += window;
         }
-        normalizer = 1/(normalizer * nhops);
+        normalizer = 1/(normalizer * sqrt(nhops));
         for (fp = b->b_vec[i].k_stuff, j = 0;
              j < filterpoints; j++, fp+= 2)
             fp[0] *= normalizer, fp[1] *= normalizer;
@@ -353,10 +361,10 @@ static t_filterbank *bonk_newfilterbank(int npoints, int nfilters,
 #endif
         newcf = (cf + bw/overlap)/(1 - relspace);
         newbw = newcf * overlap * relspace;
-        if (newbw < HALFWIDTH)
+        if (newbw < 0.5*minbandwidth)
         {
-            newbw = HALFWIDTH;
-            newcf = cf + 2 * HALFWIDTH / overlap;
+            newbw = 0.5*minbandwidth;
+            newcf = cf + minbandwidth / overlap;
         }
         cf = newcf;
         bw = newbw;
@@ -387,7 +395,7 @@ static void bonk_freefilterbank(t_filterbank *b)
 
 static void bonk_donew(t_bonk *x, int npoints, int period, int nsig, 
     int nfilters, float halftones, float overlap, float firstbin,
-    float samplerate)
+    float minbandwidth, float samplerate)
 {
     int i, j;
     t_hist *h;
@@ -440,7 +448,8 @@ static void bonk_donew(t_bonk *x, int npoints, int period, int nsig,
             fb->b_halftones == x->x_halftones &&
             fb->b_firstbin == firstbin &&
             fb->b_overlap == overlap &&
-            fb->b_npoints == x->x_npoints)
+            fb->b_npoints == x->x_npoints &&
+            fb->b_minbandwidth == minbandwidth)
     {
         fb->b_refcount++;
         x->x_filterbank = fb;
@@ -448,7 +457,7 @@ static void bonk_donew(t_bonk *x, int npoints, int period, int nsig,
     }
     if (!x->x_filterbank)
         x->x_filterbank = bonk_newfilterbank(npoints, nfilters, 
-            halftones, overlap, firstbin),
+            halftones, overlap, firstbin, minbandwidth),
                 x->x_filterbank->b_refcount++;
 }
 
@@ -1035,7 +1044,7 @@ static void *bonk_new(t_symbol *s, int argc, t_atom *argv)
     int nsig = 1, period = DEFPERIOD, npts = DEFNPOINTS,
         nfilters = DEFNFILTERS, j;
     float halftones = DEFHALFTONES, overlap = DEFOVERLAP,
-        firstbin = DEFFIRSTBIN;
+        firstbin = DEFFIRSTBIN, minbandwidth = DEFMINBANDWIDTH;
     t_insig *g;
 
     if (argc > 0 && argv[0].a_type == A_FLOAT)
@@ -1082,6 +1091,11 @@ static void *bonk_new(t_symbol *s, int argc, t_atom *argv)
             firstbin = atom_getfloatarg(1, argc, argv);
             argc -= 2; argv += 2;
         }
+        else if (!strcmp(firstarg->s_name, "-minbandwidth") && argc > 1)
+        {
+            minbandwidth = atom_getfloatarg(1, argc, argv);
+            argc -= 2; argv += 2;
+        }
         else if (!strcmp(firstarg->s_name, "-spew") && argc > 1)
         {
             x->x_spew = (atom_getfloatarg(1, argc, argv) != 0);
@@ -1123,7 +1137,7 @@ static void *bonk_new(t_symbol *s, int argc, t_atom *argv)
     }
     x->x_cookedout = outlet_new(&x->x_obj, gensym("list"));
     bonk_donew(x, npts, period, nsig, nfilters, halftones, overlap,
-        firstbin, sys_getsr());
+        firstbin, minbandwidth, sys_getsr());
     return (x);
 }
 
@@ -1182,6 +1196,9 @@ int main()
         attr = attr_offset_new("firstbin", sym_float32, attrflags, (method)0L, (method)0L, calcoffset(t_bonk, x_firstbin));
         class_addattr(c, attr);
         
+        attr = attr_offset_new("minbandwidth", sym_float32, attrflags, (method)0L, (method)0L, calcoffset(t_bonk, x_minbandwidth));
+        class_addattr(c, attr);
+        
         attr = attr_offset_new("minvel", sym_float32, attrflags, (method)0L, (method)bonk_minvel_set, calcoffset(t_bonk, x_minvel));
         class_addattr(c, attr);
         
@@ -1250,6 +1267,7 @@ static void *bonk_new(t_symbol *s, long ac, t_atom *av)
         x->x_nfilters = DEFNFILTERS;
         x->x_halftones = DEFHALFTONES;
         x->x_firstbin = DEFFIRSTBIN;
+        x->x_minbandwidth = DEFMINBANDWIDTH;
         x->x_overlap = DEFOVERLAP;
         x->x_ninsig = 1;
 
@@ -1305,7 +1323,8 @@ static void *bonk_new(t_symbol *s, long ac, t_atom *av)
         x->x_clock = clock_new(x, (method)bonk_tick);
 
         bonk_donew(x, x->x_npoints, x->x_period, x->x_ninsig, x->x_nfilters,
-            x->x_halftones, x->x_overlap, x->x_firstbin, sys_getsr());
+            x->x_halftones, x->x_overlap, x->x_firstbin, x->x_minbandwidth,
+                sys_getsr());
     }
     return (x);
 }
diff --git a/src/g_canvas.c b/src/g_canvas.c
index d324fbd2c79294ce28c9ee876e21907acbf4062f..fd2df161442f17115cdf9bc0054a7b048b40f3d4 100644
--- a/src/g_canvas.c
+++ b/src/g_canvas.c
@@ -602,7 +602,8 @@ void canvas_reflecttitle(t_canvas *x)
             canvas_getdir(x)->s_name);
 }
 
-void canvas_dirty(t_canvas *x, t_int n)
+    /* mark a glist dirty or clean */
+void canvas_dirty(t_canvas *x, t_floatarg n)
 {
     t_canvas *x2 = canvas_getrootfor(x);
     if (glist_amreloadingabstractions)
@@ -686,7 +687,7 @@ void glist_menu_open(t_glist *x)
     {
         t_glist *gl2 = x->gl_owner;
         if (!gl2) 
-            bug("canvas_vis");  /* shouldn't happen but don't get too upset. */
+            bug("glist_menu_open");  /* shouldn't happen but not dangerous */
         else
         {
                 /* erase ourself in parent window */
@@ -735,7 +736,8 @@ void canvas_free(t_canvas *x)
     glist_noselect(x);
     while (y = x->gl_list)
         glist_delete(x, y);
-    canvas_vis(x, 0);
+    if (x == glist_getcanvas(x))
+        canvas_vis(x, 0);
 
     if (strcmp(x->gl_name->s_name, "Pd"))
         pd_unbind(&x->gl_pd, canvas_makebindsym(x->gl_name));
@@ -1549,6 +1551,8 @@ void g_canvas_setup(void)
         gensym("menu-open"), A_NULL);
     class_addmethod(canvas_class, (t_method)canvas_map,
         gensym("map"), A_FLOAT, A_NULL);
+    class_addmethod(canvas_class, (t_method)canvas_dirty,
+        gensym("dirty"), A_FLOAT, A_NULL);
     class_setpropertiesfn(canvas_class, (t_propertiesfn)canvas_properties);
 
 /* ---------------------- list handling ------------------------ */
diff --git a/src/g_canvas.h b/src/g_canvas.h
index d79510599146e892a2fbf4c5aa2c4b81c57776e9..e36fb48b1b10aee75d064848856abeb14120e10b 100644
--- a/src/g_canvas.h
+++ b/src/g_canvas.h
@@ -467,7 +467,7 @@ EXTERN void canvas_setcurrent(t_canvas *x);
 EXTERN void canvas_unsetcurrent(t_canvas *x);
 EXTERN t_symbol *canvas_realizedollar(t_canvas *x, t_symbol *s);
 EXTERN t_canvas *canvas_getrootfor(t_canvas *x);
-EXTERN void canvas_dirty(t_canvas *x, t_int n);
+EXTERN void canvas_dirty(t_canvas *x, t_floatarg n);
 EXTERN int canvas_getfont(t_canvas *x);
 typedef int (*t_canvasapply)(t_canvas *x, t_int x1, t_int x2, t_int x3);
 
diff --git a/src/g_editor.c b/src/g_editor.c
index f8180d8a010ab4876fb746689483bbe9bd1496b2..ef096a4c0edf402824fcf700081bcfe25ac85d8e 100644
--- a/src/g_editor.c
+++ b/src/g_editor.c
@@ -27,6 +27,7 @@ static void canvas_clearline(t_canvas *x);
 static t_binbuf *copy_binbuf;
 static char *canvas_textcopybuf;
 static int canvas_textcopybufsize;
+static t_glist *glist_finddirty(t_glist *x);
 
 /* ---------------- generic widget behavior ------------------------- */
 
@@ -166,12 +167,26 @@ void glist_select(t_glist *x, t_gobj *y)
     }
 }
 
+    /* recursively deselect everything in a gobj "g", if it happens to be
+    a glist, in preparation for deselecting g itself in glist_dselect() */
+static void glist_checkanddeselectall(t_glist *gl, t_gobj *g)
+{
+    t_glist *gl2;
+    t_gobj *g2;
+    if (pd_class(&g->g_pd) != canvas_class)
+        return;
+    gl2 = (t_glist *)g;
+    for (g2 = gl2->gl_list; g2; g2 = g2->g_next)
+        glist_checkanddeselectall(gl2, g2);
+    glist_noselect(gl2);
+}
+
     /* call this for selected objects only */
 void glist_deselect(t_glist *x, t_gobj *y)
 {
     int fixdsp = 0;
     static int reenter = 0;
-    if (reenter) return;
+    /* if (reenter) return; */
     reenter = 1;
     if (x->gl_editor)
     {
@@ -187,6 +202,7 @@ void glist_deselect(t_glist *x, t_gobj *y)
                 {
                     z = fuddy;
                     canvas_stowconnections(glist_getcanvas(x));
+                    glist_checkanddeselectall(x, y);
                 }
                 gobj_activate(y, x, 0);
             }
@@ -1537,7 +1553,7 @@ static void canvas_doregion(t_canvas *x, int xpos, int ypos, int doit)
         else hiy = x->gl_editor->e_ywas, loy = ypos;
         canvas_selectinrect(x, lox, loy, hix, hiy);
         sys_vgui(".x%lx.c delete x\n", x);
-        x->gl_editor->e_onmotion = 0;
+        x->gl_editor->e_onmotion = MA_NONE;
     }
     else sys_vgui(".x%lx.c coords x %d %d %d %d\n",
             x, x->gl_editor->e_xwas,
@@ -1568,8 +1584,25 @@ void canvas_mouseup(t_canvas *x,
             /* after motion, if there's only one item selected, activate it */
         if (x->gl_editor->e_selection &&
             !(x->gl_editor->e_selection->sel_next))
-                gobj_activate(x->gl_editor->e_selection->sel_what,
-                    x, 1);
+        {
+            t_gobj *g = x->gl_editor->e_selection->sel_what;
+            t_glist *gl2;
+                /* first though, check we aren't an abstraction with a
+                dirty sub-patch that would be discarded if we edit this. */
+            if (pd_class(&g->g_pd) == canvas_class &&
+                canvas_isabstraction((t_glist *)g) &&
+                    (gl2 = glist_finddirty((t_glist *)g)))
+            {
+                vmess(&gl2->gl_pd, gensym("menu-open"), "");
+                x->gl_editor->e_onmotion = MA_NONE;
+                sys_vgui(
+"pdtk_check {Discard changes to '%s'?} {.x%lx dirty 0;\n} no\n",
+                    canvas_getrootfor(gl2)->gl_name->s_name, gl2);
+                return;
+            }
+                /* OK, activate it */
+            gobj_activate(x->gl_editor->e_selection->sel_what, x, 1);
+        }
     }
     if (x->gl_editor->e_onmotion != MA_NONE)
         sys_vgui("pdtk_canvas_getscroll .x%lx.c\n", x);
@@ -1597,7 +1630,8 @@ static void canvas_displaceselection(t_canvas *x, int dx, int dy)
     }
     if (resortin) canvas_resortinlets(x);
     if (resortout) canvas_resortoutlets(x);
-    canvas_dirty(x, 1);
+    if (x->gl_editor->e_selection)
+        canvas_dirty(x, 1);
 }
 
     /* this routine is called whenever a key is pressed or released.  "x"
@@ -1674,6 +1708,10 @@ void canvas_key(t_canvas *x, t_symbol *s, int ac, t_atom *av)
         return;
     if (x && down)
     {
+        t_object *ob;
+            /* cancel any dragging action */
+        if (x->gl_editor->e_onmotion == MA_MOVE)
+            x->gl_editor->e_onmotion = MA_NONE;
             /* if an object has "grabbed" keys just send them on */
         if (x->gl_editor->e_grab
             && x->gl_editor->e_keyfn && keynum)
@@ -1687,15 +1725,34 @@ void canvas_key(t_canvas *x, t_symbol *s, int ac, t_atom *av)
             || !strcmp(gotkeysym->s_name, "Left")
             || !strcmp(gotkeysym->s_name, "Right")))
         {
-            if (!x->gl_editor->e_textdirty)
+                /* special case - carriage return to object "makes" it */
+            if (keynum == '\n' && (ob =
+                pd_checkobject(&x->gl_editor->e_selection->sel_what->g_pd)) &&
+                    ob->te_type == T_OBJECT)
             {
-                canvas_setundo(x, canvas_undo_cut,
-                    canvas_undo_set_cut(x, UCUT_TEXT), "typing");
+                t_gobj *g;
+                int nobj, indx =
+                    canvas_getindex(x, x->gl_editor->e_selection->sel_what);
+                glist_noselect(x);
+                    /* "ob" may have disappeared; just search to the last
+                    object and select it */
+                for (g = x->gl_list, nobj = 0; g; g = g->g_next, nobj++)
+                    if (nobj == indx)
+                        glist_select(x, g);
+            }
+            else
+            {
+                    /* otherwise send the key to the box's editor */
+                if (!x->gl_editor->e_textdirty)
+                {
+                    canvas_setundo(x, canvas_undo_cut,
+                        canvas_undo_set_cut(x, UCUT_TEXT), "typing");
+                }
+                rtext_key(x->gl_editor->e_textedfor,
+                    (int)keynum, gotkeysym);
+                if (x->gl_editor->e_textdirty)
+                    canvas_dirty(x, 1);
             }
-            rtext_key(x->gl_editor->e_textedfor,
-                (int)keynum, gotkeysym);
-            if (x->gl_editor->e_textdirty)
-                canvas_dirty(x, 1);
         }
             /* check for backspace or clear */
         else if (keynum == 8 || keynum == 127)
@@ -1783,6 +1840,7 @@ void canvas_startmotion(t_canvas *x)
 }
 
 /* ----------------------------- window stuff ----------------------- */
+extern int sys_perf;
 
 void canvas_print(t_canvas *x, t_symbol *s)
 {
@@ -1815,11 +1873,11 @@ void glob_verifyquit(void *dummy, t_floatarg f)
     {
         canvas_vis(g2, 1);
         sys_vgui(
-"pdtk_check {Discard changes to this window??} {.x%lx menuclose 3;\n} no\n",
-                g2);
+"pdtk_check {Discard changes to '%s'?} {.x%lx menuclose 3;\n} no\n",
+                canvas_getrootfor(g2)->gl_name->s_name, g2);
         return;
     }
-    if (f == 0)
+    if (f == 0 && sys_perf)
         sys_vgui("pdtk_check {really quit?} {pd quit;\n} yes\n");
     else glob_quit(0);
 }
@@ -1842,18 +1900,19 @@ void canvas_menuclose(t_canvas *x, t_floatarg fforce)
         g = glist_finddirty(x);
         if (g)
         {
-            canvas_vis(g, 1);
+            vmess(&g->gl_pd, gensym("menu-open"), "");
             sys_vgui(
-"pdtk_check {Discard changes to this window??} {.x%lx menuclose 2;\n} no\n",
-                g);
+"pdtk_check {Discard changes to '%s'?} {.x%lx menuclose 2;\n} no\n",
+                canvas_getrootfor(g)->gl_name->s_name, g);
             return;
         }
-        else
+        else if (sys_perf)
         {
             sys_vgui(
-"pdtk_check {Close this window??} {.x%lx menuclose 1;\n} yes\n",
-                x);
+"pdtk_check {Close '%s'?} {.x%lx menuclose 1;\n} yes\n",
+                canvas_getrootfor(x)->gl_name->s_name, x);
         }
+        else pd_free(&x->gl_pd);
     }
     else if (force == 1)
         pd_free(&x->gl_pd);
@@ -1865,10 +1924,10 @@ void canvas_menuclose(t_canvas *x, t_floatarg fforce)
         g = glist_finddirty(x);
         if (g)
         {
-            canvas_vis(g, 1);
+            vmess(&g->gl_pd, gensym("menu-open"), "");
             sys_vgui(
-"pdtk_check {Discard changes to this window??} {.x%lx menuclose 2;\n} no\n",
-                g);
+"pdtk_check {Discard changes to '%s'?} {.x%lx menuclose 2;\n} no\n",
+                canvas_getrootfor(x)->gl_name->s_name, g);
             return;
         }
         else pd_free(&x->gl_pd);
@@ -2349,10 +2408,10 @@ void canvas_connect(t_canvas *x, t_floatarg fwhoout, t_floatarg foutno,
         as needed */ 
     if (pd_class(&src->g_pd) == text_class && objsrc->te_type == T_OBJECT)
         while (outno >= obj_noutlets(objsrc))
-            outlet_new(objsrc, &s_);
+            outlet_new(objsrc, 0);
     if (pd_class(&sink->g_pd) == text_class && objsink->te_type == T_OBJECT)
         while (inno >= obj_ninlets(objsink))
-            inlet_new(objsink, &objsink->ob_pd, &s_, &s_);
+            inlet_new(objsink, &objsink->ob_pd, 0, 0);
 
     if (!(oc = obj_connect(objsrc, outno, objsink, inno))) goto bad;
     if (glist_isvisible(x))
diff --git a/src/g_text.c b/src/g_text.c
index 1d4559ecd36464e40b28faea2f4b734a97c92a71..30184fcfc4703f535f0290c3468eb1e69f76f3c4 100644
--- a/src/g_text.c
+++ b/src/g_text.c
@@ -131,9 +131,36 @@ static void canvas_objtext(t_glist *gl, int xpix, int ypix, int selected,
     canvas_unsetcurrent((t_canvas *)gl);
 }
 
+    /* utility routine to figure out where to put a new text box from menu
+    and whether to connect to it automatically */
+static void canvas_howputnew(t_canvas *x, int *connectp, int *xpixp, int *ypixp,
+    int *indexp, int *totalp)
+{
+    int xpix, ypix, indx = 0, nobj = 0, x1, x2, y1, y2;
+    int connectme = (x->gl_editor->e_selection &&
+        !x->gl_editor->e_selection->sel_next);
+    if (connectme)
+    {
+        t_gobj *g;
+        for (g = x->gl_list, nobj = 0; g; g = g->g_next, nobj++)
+            if (g == x->gl_editor->e_selection->sel_what)
+        {
+            gobj_getrect(g, x, &x1, &y1, &x2, &y2);
+            indx = nobj;
+            *xpixp = x1;
+            *ypixp = y2 + 5;
+        }
+    }
+    else glist_getnextxy(x, xpixp, ypixp);
+    glist_noselect(x);
+    *connectp = connectme;
+    *indexp = indx;
+    *totalp = nobj;
+}
+
     /* object creation routine.  These are called without any arguments if
-    they're invoked from the
-    gui; when pasting or restoring from a file, we get at least x and y. */
+    they're invoked from the gui; when pasting or restoring from a file, we
+    get at least x and y. */
 
 void canvas_obj(t_glist *gl, t_symbol *s, int argc, t_atom *argv)
 {
@@ -145,18 +172,20 @@ void canvas_obj(t_glist *gl, t_symbol *s, int argc, t_atom *argv)
         canvas_objtext(gl, atom_getintarg(0, argc, argv),
             atom_getintarg(1, argc, argv), 0, b);
     }
-    else if(!glist_isvisible(gl)){
-      /* JMZ: not a good idea to go into interactive mode in a closed canvas... */
-      post("unable to create stub object in closed canvas!");
-      return;
-    } else {
+        /* JMZ: don't go into interactive mode in a closed canvas */
+    else if (!glist_isvisible(gl))
+        post("unable to create stub object in closed canvas!");
+    else
+    {
+            /* interactively create new obect */
         t_binbuf *b = binbuf_new();
-        int xpix, ypix;
+        int connectme, xpix, ypix, indx, nobj;
+        canvas_howputnew(gl, &connectme, &xpix, &ypix, &indx, &nobj);
         pd_vmess(&gl->gl_pd, gensym("editmode"), "i", 1);
-        glist_noselect(gl);
-        glist_getnextxy(gl, &xpix, &ypix);
         canvas_objtext(gl, xpix, ypix, 1, b);
-        canvas_startmotion(glist_getcanvas(gl));
+        if (connectme)
+            canvas_connect(gl, indx, 0, nobj, 0);
+        else canvas_startmotion(glist_getcanvas(gl));
     }
 }
 
@@ -417,24 +446,23 @@ void canvas_msg(t_glist *gl, t_symbol *s, int argc, t_atom *argv)
         if (argc > 2) binbuf_restore(x->m_text.te_binbuf, argc-2, argv+2);
         glist_add(gl, &x->m_text.te_g);
     }
+    else if (!glist_isvisible(gl))
+        post("unable to create stub message in closed canvas!");
     else
     {
-        int xpix, ypix;
-        /* JMZ: not a good idea to go into interactive mode in a closed canvas... */
-        if(!glist_isvisible(gl)){
-          post("unable to create stub message in closed canvas!");
-          return;
-        }
+        int connectme, xpix, ypix, indx, nobj;
+        canvas_howputnew(gl, &connectme, &xpix, &ypix, &indx, &nobj);
+        
         pd_vmess(&gl->gl_pd, gensym("editmode"), "i", 1);
-        glist_noselect(gl);
-        glist_getnextxy(gl, &xpix, &ypix);
-        x->m_text.te_xpix = xpix-3;
-        x->m_text.te_ypix = ypix-3;
+        x->m_text.te_xpix = xpix;
+        x->m_text.te_ypix = ypix;
         glist_add(gl, &x->m_text.te_g);
         glist_noselect(gl);
         glist_select(gl, &x->m_text.te_g);
         gobj_activate(&x->m_text.te_g, gl, 1);
-        canvas_startmotion(glist_getcanvas(gl));
+        if (connectme)
+            canvas_connect(gl, indx, 0, nobj, 0);
+        else canvas_startmotion(glist_getcanvas(gl));
     }
 }
 
@@ -876,19 +904,20 @@ void canvas_atom(t_glist *gl, t_atomtype type,
     }
     else
     {
-        int xpix, ypix;
+        int connectme, xpix, ypix, indx, nobj;
+        canvas_howputnew(gl, &connectme, &xpix, &ypix, &indx, &nobj);
         outlet_new(&x->a_text,
             x->a_atom.a_type == A_FLOAT ? &s_float: &s_symbol);
         inlet_new(&x->a_text, &x->a_text.te_pd, 0, 0);
         pd_vmess(&gl->gl_pd, gensym("editmode"), "i", 1);
-        glist_noselect(gl);
-        glist_getnextxy(gl, &xpix, &ypix);
         x->a_text.te_xpix = xpix;
         x->a_text.te_ypix = ypix;
         glist_add(gl, &x->a_text.te_g);
         glist_noselect(gl);
         glist_select(gl, &x->a_text.te_g);
-        canvas_startmotion(glist_getcanvas(gl));
+        if (connectme)
+            canvas_connect(gl, indx, 0, nobj, 0);
+        else canvas_startmotion(glist_getcanvas(gl));
     }
 }
 
@@ -1321,10 +1350,18 @@ void text_setto(t_text *x, t_glist *glist, char *buf, int bufsize)
     else binbuf_text(x->te_binbuf, buf, bufsize);
 }
 
+    /* this gets called when amessage gets sent to an object whose creation
+    failed, presumably because of loading a patch with a missing extern or
+    abstraction */
+static void text_anything(t_text *x, t_symbol *s, int argc, t_atom *argv)
+{
+}
+
 void g_text_setup(void)
 {
     text_class = class_new(gensym("text"), 0, 0, sizeof(t_text),
         CLASS_NOINLET | CLASS_PATCHABLE, 0);
+    class_addanything(text_class, text_anything);
 
     message_class = class_new(gensym("message"), 0, (t_method)message_free,
         sizeof(t_message), CLASS_PATCHABLE, 0);
diff --git a/src/m_glob.c b/src/m_glob.c
index e3b49249d9fe53608eecc1215157fcd53e0b9c36..ca3b5d1aff388b8040c03490d50c18155b4885a7 100644
--- a/src/m_glob.c
+++ b/src/m_glob.c
@@ -8,6 +8,8 @@
 t_class *glob_pdobject;
 static t_class *maxclass;
 
+int sys_perf;   /* true if we should query user on close and quit */
+
 /* These "glob" routines, which implement messages to Pd, are from all
 over.  Some others are prototyped in m_imp.h as well. */
 
@@ -55,6 +57,11 @@ static void glob_version(t_pd *dummy, float f)
         error("file format newer than this version of Pd (trying anyway...)");
 }
 
+static void glob_perf(t_pd *dummy, float f)
+{
+    sys_perf = (f != 0);
+}
+
 void max_default(t_pd *x, t_symbol *s, int argc, t_atom *argv)
 {
     int i;
@@ -121,6 +128,8 @@ void glob_init(void)
         gensym("save-preferences"), 0);
     class_addmethod(glob_pdobject, (t_method)glob_version,
         gensym("version"), A_FLOAT, 0);
+    class_addmethod(glob_pdobject, (t_method)glob_perf,
+        gensym("perf"), A_FLOAT, 0);
 #ifdef UNIX
     class_addmethod(glob_pdobject, (t_method)glob_watchdog,
         gensym("watchdog"), 0);
diff --git a/src/m_pd.h b/src/m_pd.h
index 26f1126f323f9a0436e54786e47a084c173e1718..888ed9365eb9f6cd70213a2b48e7a075a9ed76f1 100644
--- a/src/m_pd.h
+++ b/src/m_pd.h
@@ -11,7 +11,7 @@ extern "C" {
 #define PD_MAJOR_VERSION 0
 #define PD_MINOR_VERSION 42
 #define PD_BUGFIX_VERSION 0
-#define PD_TEST_VERSION "test7"
+#define PD_TEST_VERSION "test8"
 
 /* old name for "MSW" flag -- we have to take it for the sake of many old
 "nmakefiles" for externs, which will define NT and not MSW */
diff --git a/src/makefile.in b/src/makefile.in
index ab6209ffa950741c3728c5aebcd44bacbf0424aa..cba0f7a8fba98227ee515b98d8f15377788e606f 100644
--- a/src/makefile.in
+++ b/src/makefile.in
@@ -163,32 +163,33 @@ externs:
 
 BINARYMODE=@binarymode@
 
-ABOUT_FILE=$(pddocdir)/1.manual/1.introduction.txt
+ABOUT_FILE=$(DESTDIR)$(pddocdir)/1.manual/1.introduction.txt
 install:  all
-	install -d $(libpdbindir)
-	install $(BIN_DIR)/$(GUINAME) $(libpdbindir)/$(GUINAME)
-	install $(BIN_DIR)/pd-watchdog $(libpdbindir)/pd-watchdog
-	install -m644 $(BIN_DIR)/pd.tk $(libpdbindir)/pd.tk
+	install -d $(DESTDIR)$(libpdbindir)
+	install $(BIN_DIR)/$(GUINAME) $(DESTDIR)$(libpdbindir)/$(GUINAME)
+	install $(BIN_DIR)/pd-watchdog $(DESTDIR)$(libpdbindir)/pd-watchdog
+	install -m644 $(BIN_DIR)/pd.tk $(DESTDIR)$(libpdbindir)/pd.tk
 	install -d $(DESTDIR)$(bindir)
 	install $(BINARYMODE) $(PDEXEC) $(DESTDIR)$(bindir)/pd
 	install -m755 $(BIN_DIR)/pdsend $(DESTDIR)$(bindir)/pdsend
 	install -m755 $(BIN_DIR)/pdreceive $(DESTDIR)$(bindir)/pdreceive 
 	for dir in $(shell ls -1 ../doc | grep -v CVS); do \
 		echo "installing $$dir"; \
-		install -d $(pddocdir)/$$dir ; \
-		install -m644 -p ../doc/$$dir/*.* $(pddocdir)/$$dir ; \
+		install -d $(DESTDIR)$(pddocdir)/$$dir ; \
+		install -m644 -p ../doc/$$dir/*.* $(DESTDIR)$(pddocdir)/$$dir ; \
 	done
 	for dir in $(shell ls -1 ../doc/7.stuff | grep -v CVS); do \
 		echo "installing 7.stuff/$$dir"; \
-		install -d $(pddocdir)/7.stuff/$$dir ; \
-		install -m644 -p ../doc/7.stuff/$$dir/*.* $(pddocdir)/7.stuff/$$dir ; \
+		install -d $(DESTDIR)$(pddocdir)/7.stuff/$$dir ; \
+		install -m644 -p ../doc/7.stuff/$$dir/*.* \
+                    $(DESTDIR)$(pddocdir)/7.stuff/$$dir ; \
 	done
 	mv $(ABOUT_FILE) $(ABOUT_FILE).tmp
 	cat $(ABOUT_FILE).tmp | sed 's|PD_VERSION|Pd version $(PD_VERSION)|' \
 		> $(ABOUT_FILE)
 	rm $(ABOUT_FILE).tmp
-	cp -pr ../extra $(libpddir)/
-	rm -f $(libpddir)/extra/*/*.o
+	cp -pr ../extra $(DESTDIR)$(libpddir)/
+	rm -f $(DESTDIR)$(libpddir)/extra/*/*.o
 	install -d $(DESTDIR)$(includedir)
 	install -m644 m_pd.h $(DESTDIR)$(includedir)/m_pd.h
 	install -d $(DESTDIR)$(mandir)/man1
@@ -230,7 +231,7 @@ makefile.dependencies: makefile
 	$(CC) $(CPPFLAGS) -M $(SRC) > makefile.dependencies
 
 uninstall:
-	rm -f -r $(libpddir)
+	rm -f -r $(DESTDIR)$(libpddir)
 	rm -f $(DESTDIR)$(bindir)/pd
 	rm -f $(DESTDIR)$(bindir)/pdsend
 	rm -f $(DESTDIR)$(bindir)/pdreceive
diff --git a/src/notes.txt b/src/notes.txt
index 4a7c25102bc0b14aa9f1c3872117d22c8095da4b..fabdc33273759225dafc5a6011637729b3fe7811 100644
--- a/src/notes.txt
+++ b/src/notes.txt
@@ -1,5 +1,5 @@
 ---------------- dolist --------------------
-reproduce canvas complaint:  find "s scatsinger" &  move any box...
+close patch with dirty GOP abstraction can't vis the abstraction to query
 
 test:
 compile on various versions of linux
@@ -15,6 +15,7 @@ mac:
 Gnome: why don't windows pop up when clicked on?
 
 problems:
+saving as "x.pd" on mac writes to "x.pd.pd"
 if .pdsettings asks for an alsa device out of range, dialog
   comes up with empty list
 find asdf$1 (e.g.) doesn't work