diff --git a/pd/src/g_editor.c b/pd/src/g_editor.c index 48c267b982a52e1f5e1c21ef1aac8b7b450679c5..60c13caf541d1ec27271cbd61984860c68a5db5a 100644 --- a/pd/src/g_editor.c +++ b/pd/src/g_editor.c @@ -4979,53 +4979,73 @@ int canvas_trymulticonnect(t_canvas *x, int xpos, int ypos, int which, int doit) /* end of THIRD OPTION */ } - /* FOURTH OPTION (1:N a.k.a. fan-out or N:1 a.k.a. fan-in - multi-connect): These are multi-connect variations of the fan-out - and fan-in single-connect operations, in which at least *three* - objects are selected and *both* the originating object y1 and the - receiving object y2 are selected. It connects each originating - object's outlet to each receiving object's inlets until it runs out - of objects or outlets. + /* FOURTH OPTION (1:N a.k.a. fan-out, N:1 a.k.a. fan-in, or N:N + parallel multi-connect): At least *three* objects are selected + which include *both* the originating object y1 and the receiving + object y2. This is the most versatile but also the most complicated + option. The precise outcome depends on the initial connection and + the status of the ctrl key when doing the initial connection, see + options A-C below. Since both y1 and y2 are selected, there's no a-priori way to tell - which of the selected objects are originating and which are + which of the selected objects will be originating and which will be receiving connections, so a visual top-to-bottom and left-to-right - order is used instead. For the sake of simplicity, assume that the - objects are arranged in two rows a, b as depicted below, where y1 = - a[i] and y2 = b[j]: + order is used instead. To keep things simple, let's imagine that + the objects are arranged in two rows a, b as depicted below, where + y1 = a[i] and y2 = b[j]: a[1] ... a[i-1] y1 a[i+1] ... a[m] b[1] ... b[j-1] y2 b[j+1] ... b[n] - Then one of the following options will be taken: + Then one of the following options A-C will be taken. Option A and B + are multi-connect variations of the fan-out and fan-in (mode 2 and + 3) single-connect operations above. Option C is only active when + holding the ctrl key while doing the connection. It produces N:N + parallel connections. - OPTION A (fan-out): Beginning with the *outlet* of the initial connection y1-y2, the outlets of y1 will be connected to the same - inlet of as many objects y2=b[j], b[j+1], ... as possible, in - that order, starting with y2=b[j]. + inlet of as many objects b[j], b[j+1], ... as possible, in that + order, starting with y2=b[j]. - OPTION B (fan-in): Beginning with the *inlet* of the initial connection y1-y2, the inlets of y2 will be connected to the same - outlet of as many objects y1=a[i], a[i+1], ... as possible, in - that order, starting with y1=a[i]. - - The operation automatically chooses fan-out or fan-in depending on - which option gives the larger number of connections, breaking ties - in favor of fan-out. However, you can reverse that decision by - pressing the ctrl key while connecting. Note that the operation - will fail if y2 comes *before* y1 in the visual order, since - connections will always go to objects which come later in the - visual order. - - CAVEATS: This operation is not 100% foolproof. :) The current + outlet of as many objects a[i], a[i+1], ... as possible, in that + order, starting with y1=a[i]. + + - OPTION C (parallel connections): Beginning with the initial y1-y2 + connection, connect as many pairs y1=a[i] -> y2=b[j], a[i+1] -> + b[j+1], ... as possible, using the same inlet and outlet numbers + as in the initial connection. + + Note that in any case, the operation will fail if y2 comes *before* + y1 in the visual order, since connections will always go to objects + which come later in that order. In addition, the algorithm enforces + a strict top-to-bottom, left-to-right order of doing connections. + This limits the possible connections, but makes the results more + predictable and more likely to resemble the user's intentions. + + Option C is only taken if the ctrl key is pressed while doing the + initial connection. Otherwise, the operation automatically chooses + fan-out or fan-in depending on which option gives you the larger + number of connections, breaking ties in favor of fan-out. + + CAVEAT: This operation is not 100% foolproof. :) The current implementation doesn't really try to arrange the selected objects into two neat rows as depicted above, it only goes by the precomputed linear top-to-bottom, left-to-right order. In the real - world, patches tend to be messy, with objects not being lined up - exactly. The operation tries to account for this by rounding - coordinates to a 10 pixel grid (see grid_coord() above); but if - necessary you can always use the tidy-up operation in the edit menu - to line things up properly beforehand. */ + world, patches tend to be messy, with objects rarely being lined up + perfectly. The algorithm tries to account for this by rounding + coordinates to a 10 pixel grid (see grid_coord() above), but you + can help it along by employing the tidy-up operation in the edit + menu to line things up properly beforehand. + + It also helps if you avoid ambiguities by selecting minimal 'a' and + 'b' sets of objects which will give you the connections that you + want. In particular, if the algorithm gives you a fan-out, but you + actually wanted a fan-in instead, try deselecting all 'b' objects + except the one that you want to fan into. In the same way, a + fan-out can be forced by selecting only a single 'a' object. */ else if (x->gl_editor->e_selection->sel_next->sel_next && glist_isselected(x, y1) && glist_isselected(x, y2)) { @@ -5068,8 +5088,68 @@ int canvas_trymulticonnect(t_canvas *x, int xpos, int ypos, int which, int doit) // now that we made the initial connection and know where to // begin and where to connect to, let's connect the rest t_selection *sel; - // resort selection + int i, i1 = -1, i2 = -1; + int last_x = -1; + // re-order the selection canvas_sort_selection_according_to_location(x); + + if (glob_ctrl) { + // OPTION C (N:N multi-connect) + t_selection *sel1 = NULL; + int count = 0, counting = 0; + for (sel = x->gl_editor->e_selection, i = 0; sel; sel = sel->sel_next, i++) + { + t_text *yt = (t_text *)(sel->sel_what); + if (grid_coord(yt->te_xpix) < last_x) { + // enforce left-to-right order + if (counting) + counting = 0; + else if (i2 != -1) + break; + } else { + last_x = grid_coord(yt->te_xpix); + } + // identify original source and target as we go along + if (sel->sel_what == y1) { + i1 = i; + // found the original source + sel1 = sel; + // start counting + counting = 1; + } else if (sel->sel_what == y2) { + i2 = i; + last_x = -1; + // original target comes before source => fail + if (i1 == -1) break; + // otherwise we found the original target, start + // doing connections + counting = 0; + } else if (i2 != -1) { + // still doing connections + sel1 = sel1->sel_next; + // connected as many sources as we got => done + if (!sel1 || --count < 0) break; + // connect the current source to the current + // target, using the original outlet and inlet + // numbers + ob1 = pd_checkobject(&sel1->sel_what->g_pd); + noutlet1 = obj_noutlets(ob1); + ob2 = pd_checkobject(&sel->sel_what->g_pd); + ninlet2 = obj_ninlets(ob2); + if (closest1 < noutlet1 && closest2 < ninlet2) + { + return_val = canvas_doconnect_doit( + x, sel1->sel_what, sel->sel_what, + closest1, closest2, 1, 1); + } + } else if (counting) { + count++; + } + } + canvas_undo_add(x, UNDO_SEQUENCE_END, "multiconnect (mode-4)", 0); + return(return_val); + } + // now check for OPTION A vs. B (see description above) int successA = 0; int successB = 0; @@ -5082,15 +5162,22 @@ int canvas_trymulticonnect(t_canvas *x, int xpos, int ypos, int which, int doit) t_object *tmp_ob2 = ob2; int tmp_noutlet1 = noutlet1; int tmp_ninlet2 = ninlet2; - int i, i1 = -1, i2 = -1; for (sel = x->gl_editor->e_selection, i = 0; sel; sel = sel->sel_next, i++) { + t_text *yt = (t_text *)(sel->sel_what); + if (grid_coord(yt->te_xpix) < last_x) { + // enforce left-to-right order + if (i2 != -1) break; + } else { + last_x = grid_coord(yt->te_xpix); + } // identify original source and target as we go along if (sel->sel_what == y1) { i1 = i; } else if (sel->sel_what == y2) { i2 = i; - // if original target comes before original source => fail + last_x = -1; + // original target comes before source => fail if (i1 == -1) break; // otherwise, start doing connections } else if (i2 != -1) { @@ -5141,14 +5228,12 @@ int canvas_trymulticonnect(t_canvas *x, int xpos, int ypos, int which, int doit) tmp_noutlet1 = noutlet1; tmp_ninlet2 = ninlet2; i1 = -1; i2 = -1; - int last_x = -1; + last_x = -1; for (sel = x->gl_editor->e_selection, i = 0; sel; sel = sel->sel_next, i++) { t_text *yt = (t_text *)(sel->sel_what); if (grid_coord(yt->te_xpix) < last_x) { - // this most likely indicates that we processed all 'a' - // objects already and are about to spill over into - // the 'b's, bail out. + // enforce left-to-right order break; } else { last_x = grid_coord(yt->te_xpix); @@ -5203,24 +5288,30 @@ int canvas_trymulticonnect(t_canvas *x, int xpos, int ypos, int which, int doit) // now decide which one is better // (we give preference to option A if both are equal) - // AG: Also take into account the ctrl mod status, so - // that the user can reverse our default choice (otherwise - // we usually just end up preferring fan-out) i1 = -1; i2 = -1; last_x = -1; - if (glob_ctrl ? successA < successB : successA >= successB) + if (successA >= successB) { // OPTION A (fan-out) for (sel = x->gl_editor->e_selection, i = 0; sel; sel = sel->sel_next, i++) { + t_text *yt = (t_text *)(sel->sel_what); + if (grid_coord(yt->te_xpix) < last_x) { + // enforce left-to-right order + if (i2 != -1) break; + } else { + last_x = grid_coord(yt->te_xpix); + } // identify original source and target as we go along if (sel->sel_what == y1) { i1 = i; } else if (sel->sel_what == y2) { i2 = i; - // if original target comes before original source => fail + last_x = -1; + // original target comes before source => fail if (i1 == -1) break; - // otherwise, found the original target, start doing connections + // otherwise, found the original target, start + // doing connections } else if (i2 != -1) { // still doing connections ob2 = pd_checkobject(&sel->sel_what->g_pd); @@ -5248,9 +5339,7 @@ int canvas_trymulticonnect(t_canvas *x, int xpos, int ypos, int which, int doit) { t_text *yt = (t_text *)(sel->sel_what); if (grid_coord(yt->te_xpix) < last_x) { - // this most likely indicates that we processed all 'a' - // objects already and are about to spill over into - // the 'b's, bail out. + // enforce left-to-right order break; } else { last_x = grid_coord(yt->te_xpix); @@ -5258,7 +5347,8 @@ int canvas_trymulticonnect(t_canvas *x, int xpos, int ypos, int which, int doit) // identify original source and target as we go along if (sel->sel_what == y1) { i1 = i; - // found the original source, start doing connections + // found the original source, start doing + // connections } else if (sel->sel_what == y2) { i2 = i; // reached the original target, we're done