diff --git a/pd/doc/5.reference/text-object-help.pd b/pd/doc/5.reference/text-object-help.pd index cf142a36179fd5a41a56d33496e69a5242db0cac..a887b3167d791dfb2f74cfc671d14df3ed9c7a06 100644 --- a/pd/doc/5.reference/text-object-help.pd +++ b/pd/doc/5.reference/text-object-help.pd @@ -1,5 +1,5 @@ #N struct text-help-struct float x float y text z; -#N canvas 976 92 622 580 12; +#N canvas 628 105 622 580 12; #X obj 131 529 list; #X obj 18 6 text; #X text 25 528 see also:; @@ -12,16 +12,16 @@ #X obj 280 217 text define; #X text 81 173 The text object's first argument sets its function: , f 30; -#N canvas 1163 93 631 733 define 0; +#N canvas 725 103 631 758 define 0; #X msg 39 141 clear; #X msg 46 167 read text-object-help.txt; #X msg 51 196 write text-object-help.txt; #X text 32 20 "text define" maintains a text object and can name it so that other objects can find it (and later should have some alternative \, anonymous way to be found).; -#X text 91 416 click to open and edit text:; -#X text 296 404 creation arguments:; -#X text 306 439 optional name; +#X text 91 456 click to open and edit text:; +#X text 296 444 creation arguments:; +#X text 306 479 optional name; #X text 77 263 (optionally you can read or write to/from a file interpreting carriage returns as separators \; this should allow reading some text file formats - like this:); @@ -29,33 +29,47 @@ file formats - like this:); #X text 257 196 write to a file; #X obj 58 228 bng 15 250 50 0 empty empty empty 17 7 0 10 -262144 -1 -1; -#X obj 38 531 t b p; -#X obj 38 580 text get -s text t; -#X msg 39 555 0; -#X obj 38 630 print; -#X obj 38 605 list trim; +#X obj 38 571 t b p; +#X obj 38 620 text get -s text t; +#X msg 39 595 0; +#X obj 38 670 print; +#X obj 38 645 list trim; #X text 79 222 bang to output a pointer to a scalar (struct) containing the text, f 35; -#X obj 38 440 text define -k text-help-1; +#X obj 38 480 text define -k text-help-1; #A set this is a message \; this is another 1 ... \;; #X msg 149 326 write -c /tmp/test-cr.txt; #X msg 68 354 send text-help-send; #X text 213 354 send pointer to a named receive object; #X obj 438 110 r text-help-send; #X obj 438 139 print; -#X obj 217 470 print notify-outlet; -#X text 215 498 Second outlet notifies you when text changes. As of +#X obj 217 510 print notify-outlet; +#X text 215 538 Second outlet notifies you when text changes. As of Pd 0.48 this only outputs the message "updated" when text changes \, but this might be extended to offer more information in the future. , f 42; -#X text 119 616 First outlet is pointer to a "text" scalar containing +#X text 119 656 First outlet is pointer to a "text" scalar containing the text \, which is output when the object is sent a "bang". For example \, here's machinery for printing out first line of text., f 46; #X text 107 139 clear the object's state; -#X msg 105 384 click; -#X msg 153 384 close; -#X text 201 384 open and close text window; -#X text 304 422 optional -k flag to save contents with patch; +#X msg 105 424 click; +#X msg 153 424 close; +#X text 201 424 open and close text window; +#X text 304 462 optional -k flag to save contents with patch; +#X msg 90 390 sort; +#X text 130 391 comment; +#N canvas 1071 152 647 361 sorting-text 0; +#X obj 45 126 text define text-help-sorting; +#X msg 76 89 sort; +#X msg 44 54 set zz \; yy \; 1 2 \; 1 2 3 \; 1 \; 2 \;; +#X text 63 168 Numbers come before symbols \, which are sorted alphabetically +(details such as case sensitivity (does 'b' come before 'A'?) may depend +on operating system). Shorter lines come before longer ones that match +the entire shorter lines. As a special case empty lines come before +anything else.; +#X connect 1 0 0 0; +#X connect 2 0 0 0; +#X restore 402 423 pd sorting-text; #X connect 0 0 17 0; #X connect 1 0 17 0; #X connect 2 0 17 0; @@ -72,6 +86,7 @@ the text \, which is output when the object is sent a "bang". For example #X connect 21 0 22 0; #X connect 27 0 17 0; #X connect 28 0 17 0; +#X connect 31 0 17 0; #X restore 392 217 pd define; #X obj 280 240 text get; #N canvas 885 156 859 566 get 0; diff --git a/pd/src/x_text.c b/pd/src/x_text.c index 922a5e64d1ad633147d87c42d9eea0cb3a71bae0..333433a86c1e76c9dc863cc585dd197b3fb44937 100644 --- a/pd/src/x_text.c +++ b/pd/src/x_text.c @@ -10,6 +10,8 @@ moment it also defines "text" but it may later be better to split this off. */ #include "s_stuff.h" /* just for sys_hostfontsize, phooey */ #include <string.h> #include <stdio.h> +#define __USE_GNU +#include <stdlib.h> #ifdef HAVE_UNISTD_H #include <unistd.h> #endif @@ -27,6 +29,10 @@ static t_class *text_define_class; # include <stdlib.h> /* BSDs for example */ #endif +#ifdef _WIN32 +#define qsort_r qsort_s /* of course Microsoft decides to be different */ +#endif + #ifndef HAVE_ALLOCA /* can work without alloca() but we never need it */ #define HAVE_ALLOCA 1 #endif @@ -421,6 +427,201 @@ static void text_define_send(t_text_define *x, t_symbol *s) } } +typedef struct _keyinfo +{ + int ki_forward; /* one if forward, -1 if reversed */ + int ki_onset; /* number of fields to skip over */ +} t_keyinfo; + + /* apple products seem to have their own prototypes for qsort_r (?) */ +#ifdef __APPLE__ +static int text_sortcompare(void *zkeyinfo, const void *z1, const void *z2) +#else +static int text_sortcompare(const void *z1, const void *z2, void *zkeyinfo) +#endif +{ + const t_atom *a1 = *(t_atom **)z1, *a2 = *(t_atom **)z2; + t_keyinfo *k = (t_keyinfo *)zkeyinfo; + int count; + /* advance first line by key onset and react if we run out early */ + for (count = k->ki_onset; count--; a1++) + { + if (a1->a_type == A_SEMI || a1->a_type == A_COMMA) + { + /* if second line runs out early too consider them equal */ + for (count = k->ki_onset; count--; a2++) + if (a2->a_type == A_SEMI || a2->a_type == A_COMMA) + goto equal; + return (-k->ki_forward); + } + } + for (count = k->ki_onset; count--; a2++) + if (a2->a_type == A_SEMI || a2->a_type == A_COMMA) + return (-k->ki_forward); + /* compare remaining fields */ + for (; ; a1++, a2++) + { + if (a1->a_type == A_SEMI || a1->a_type == A_COMMA) + { + /* hit end of first line */ + if (a2->a_type == A_SEMI || a2->a_type == A_COMMA) + goto equal; + else return (-k->ki_forward); + } + else if (a2->a_type == A_SEMI || a2->a_type == A_COMMA) + return (k->ki_forward); /* hit end of second line */ + + /* otherwise if they're different return something, and + if not proceed to next field */ + else if (a1->a_type == A_FLOAT) + { + if (a2->a_type == A_FLOAT) + { + if (a1->a_w.w_float < a2->a_w.w_float) + return (-k->ki_forward); + else if (a1->a_w.w_float > a2->a_w.w_float) + return (k->ki_forward); + } + else return (-k->ki_forward); + } + else if (a1->a_type == A_SYMBOL) + { + if (a2->a_type == A_SYMBOL) + { + int z = strcmp(a1->a_w.w_symbol->s_name, + a2->a_w.w_symbol->s_name); + if (z) + return (z * k->ki_forward); + } + else return (k->ki_forward); + } + } +equal: + /* ran out of both lines at same time, so we're "equal". + in this case compare pointers so that "equal" lines (which + might not be identical because of a nonzero onset) stay in the + same order as before. */ + if (a1 < a2) + return (-1); + else return (1); +} + +/* I can't seem to get to qsort_s on W2K - clicking on Pd complains it isn't +found in msvcrt (which indeed it isn't in). Rather than waste more time +on this, just call qsort if we're Microsoft and single-instance. I hope nobody +will try to compile multi-instance Pd for 32-bit windows, but if they +do, they might run into my qsort_s problem again. */ +#if defined(_WIN32) && !defined(PDINSTANCE) +#define MICROSOFT_STUPID_SORT +static void *stupid_zkeyinfo; +static int stupid_sortcompare(const void *z1, const void *z2) { + return (text_sortcompare(z1, z2, stupid_zkeyinfo)); } +#endif + + /* sort the contents */ +static void text_define_sort(t_text_define *x, t_symbol *s, + int argc, t_atom *argv) +{ + int nlines, unique = 0, natom = binbuf_getnatom(x->x_binbuf), i, + thisline, startline; + t_atom *vec = binbuf_getvec(x->x_binbuf), **sortbuf, *a1, *a2; + t_binbuf *newb; + t_keyinfo k; + k.ki_forward = 1; + k.ki_onset = 0; + while (argc && argv->a_type == A_SYMBOL && + *argv->a_w.w_symbol->s_name == '-') + { + if (!strcmp(argv->a_w.w_symbol->s_name, "-u")) + unique = 1; + else if (!strcmp(argv->a_w.w_symbol->s_name, "-r")) + k.ki_forward = -1; + else if (!strcmp(argv->a_w.w_symbol->s_name, "-k") && argc > 1 + && argv[1].a_type == A_FLOAT) + { + if ((k.ki_onset = argv[1].a_w.w_float) < 0) + k.ki_onset = 0; + argc--; argv++; + } + else + { + pd_error(x, "text define sort: unknown flag ..."); + postatom(argc, argv); endpost(); + } + argc--; argv++; + } + if (argc) + { + post("warning: text define sort ignoring extra argument: "); + postatom(argc, argv); endpost(); + } + if (!natom) + return; + /* last thing in buffer should be a terminator */ + if (vec[natom-1].a_type != A_SEMI && + vec[natom-1].a_type != A_COMMA) + binbuf_addsemi(x->x_binbuf), + vec = binbuf_getvec(x->x_binbuf), + natom = binbuf_getnatom(x->x_binbuf), + nlines++; + for (i = nlines = 0; i < natom; i++) + if (vec[i].a_type == A_SEMI || vec[i].a_type == A_COMMA) + nlines++; + sortbuf = (t_atom **)getbytes(nlines * sizeof(*sortbuf)); + for (i = thisline = 0, startline = 1; i < natom; i++) + { + if (startline) + { + if (thisline >= nlines) + bug("text_define_sort"); + sortbuf[thisline++] = vec+i; + } + startline = (vec[i].a_type == A_SEMI || vec[i].a_type == A_COMMA); + } +#ifdef MICROSOFT_STUPID_SORT + stupid_zkeyinfo = &k; + qsort(sortbuf, nlines, sizeof(*sortbuf), stupid_sortcompare); +#else +#ifdef __APPLE__ + qsort_r(sortbuf, nlines, sizeof(*sortbuf), &k, text_sortcompare); +#else /* __APPLE__ */ + qsort_r(sortbuf, nlines, sizeof(*sortbuf), text_sortcompare, &k); +#endif /* __APPLE__ */ +#endif /* MICROSOFT_STUPID_SORT */ + newb = binbuf_new(); + for (thisline = 0; thisline < nlines; thisline++) + { + if (unique && thisline > 0) /* check for duplicates */ + { + for (a1 = sortbuf[thisline-1], a2 = sortbuf[thisline]; ; a1++, a2++) + { + if (a1->a_type == A_SEMI || a1->a_type == A_COMMA) + { + if (a1->a_type == a2->a_type) + goto skipit; /* duplicate line, don't copy */ + else goto doit; + } + else if (a1->a_type != a2->a_type || + (a1->a_type == A_FLOAT && + a1->a_w.w_float != a2->a_w.w_float) || + (a1->a_type == A_SYMBOL && + a1->a_w.w_symbol != a2->a_w.w_symbol)) + goto doit; + } + } + doit: + for (i = 0, a1 = sortbuf[thisline]; + a1->a_type != A_SEMI && a1->a_type != A_COMMA; i++, a1++) + ; + binbuf_add(newb, i+1, sortbuf[thisline]); + skipit: ; + } + binbuf_free(x->x_binbuf); + x->x_scalar->sc_vec[2].w_binbuf = x->x_binbuf = newb; + freebytes(sortbuf, nlines * sizeof(*sortbuf)); + textbuf_senditup(&x->x_textbuf); +} + /* notification from GUI that we've been updated */ static void text_define_notify(t_text_define *x) { @@ -2036,6 +2237,8 @@ void x_qlist_setup(void ) gensym("read"), A_GIMME, 0); class_addmethod(text_define_class, (t_method)text_define_send, gensym("send"), A_SYMBOL, 0); + class_addmethod(text_define_class, (t_method)text_define_sort, + gensym("sort"), A_GIMME, 0); class_setsavefn(text_define_class, text_define_save); class_addbang(text_define_class, text_define_bang); class_sethelpsymbol(text_define_class, gensym("text-object")); diff --git a/scripts/regression_tests.pd b/scripts/regression_tests.pd index fc7b8832d55c3d89a664cbbb60891e8a8a54e752..097fcc19bdd84175e2b81246f24df265c1437dcb 100644 --- a/scripts/regression_tests.pd +++ b/scripts/regression_tests.pd @@ -1,4 +1,4 @@ -#N canvas 339 133 750 572 12; +#N canvas 340 157 750 572 12; #X obj 465 281 r \$0-result; #X obj 212 239 bng 31 250 50 0 empty empty Run_all 39 13 0 12 -262144 -1 -1; @@ -25,7 +25,7 @@ is handy for some binbuf tests.; #X obj 198 659 rtest makefilename_double_percent; #X obj 198 710 rtest makefilename_code_coverage; #N canvas 461 242 450 323 (subpatch) 0; -#X restore 201 2175 pd; +#X restore 201 2285 pd; #X obj 198 761 rtest makefilename_default; #X obj 198 812 rtest makefilename_default_bang; #X obj 198 863 rtest makefilename_float; @@ -58,6 +58,7 @@ is handy for some binbuf tests.; #X obj 198 1976 rtest float_symbol_method; #X obj 198 2031 rtest type_hint_coverage; #X obj 198 2086 rtest route_reject_bang; +#X obj 198 2141 rtest text_sort; #X connect 0 0 27 0; #X connect 1 0 4 0; #X connect 2 0 42 0; @@ -101,3 +102,4 @@ is handy for some binbuf tests.; #X connect 49 0 50 0; #X connect 50 0 51 0; #X connect 51 0 52 0; +#X connect 52 0 53 0; diff --git a/scripts/regression_tests/text_sort.pd b/scripts/regression_tests/text_sort.pd new file mode 100644 index 0000000000000000000000000000000000000000..4af684122da5ce37cd7b0f7d55d0534aaaf3aa3a --- /dev/null +++ b/scripts/regression_tests/text_sort.pd @@ -0,0 +1,116 @@ +#N canvas 24 67 1287 678 12; +#X obj 100 33 inlet; +#X obj 153 632 outlet; +#X obj 100 62 trigger bang bang; +#X msg 216 98 sort; +#X obj 216 132 text define -k \$0-test; +#A set z \; a \; a a \; a y \;; +#X obj 520 266 text get \$0-test; +#X msg 120 125 bang; +#X obj 520 295 list split 1; +#X obj 520 324 select a; +#X obj 610 324 select a; +#X obj 663 351 b; +#X obj 663 380 f 0; +#X obj 610 353 f 1; +#X obj 573 351 b; +#X obj 573 380 f 0; +#X obj 520 353 f 1; +#X obj 573 418 &&; +#X obj 573 447 list append sort method to [text] correctly sorted the +second line; +#X obj 750 216 text get \$0-test; +#X obj 750 274 select a; +#X obj 803 301 b; +#X obj 803 330 f 0; +#X obj 750 303 f 1; +#X obj 803 368 &&; +#X obj 803 397 list append sort method to [text] correctly sorted the +first line; +#X obj 750 245 trigger anything anything; +#X obj 840 274 list length; +#X obj 310 326 text get \$0-test; +#X obj 310 355 list split 1; +#X obj 310 384 select a; +#X obj 453 411 b; +#X obj 453 440 f 0; +#X obj 400 413 f 1; +#X obj 363 411 b; +#X obj 363 440 f 0; +#X obj 310 413 f 1; +#X obj 363 478 &&; +#X obj 400 384 select y; +#X obj 100 386 text get \$0-test; +#X obj 153 471 b; +#X obj 153 500 f 0; +#X obj 100 473 f 1; +#X obj 153 538 &&; +#X obj 100 415 trigger anything anything; +#X obj 190 444 list length; +#X obj 100 444 select z; +#X obj 100 161 trigger 3 2 1 0; +#X obj 363 507 list append sort method to [text] correctly sorted the +third line; +#X obj 153 567 list append sort method to [text] correctly sorted the +last line; +#X connect 0 0 2 0; +#X connect 2 0 46 0; +#X connect 2 1 3 0; +#X connect 3 0 4 0; +#X connect 5 0 7 0; +#X connect 6 0 46 0; +#X connect 7 0 8 0; +#X connect 7 1 9 0; +#X connect 8 0 15 0; +#X connect 8 1 13 0; +#X connect 9 0 12 0; +#X connect 9 1 10 0; +#X connect 10 0 11 0; +#X connect 11 0 16 1; +#X connect 12 0 16 1; +#X connect 13 0 14 0; +#X connect 14 0 16 0; +#X connect 15 0 16 0; +#X connect 16 0 17 0; +#X connect 17 0 1 0; +#X connect 18 0 25 0; +#X connect 19 0 22 0; +#X connect 19 1 20 0; +#X connect 20 0 21 0; +#X connect 21 0 23 0; +#X connect 22 0 23 0; +#X connect 23 0 24 0; +#X connect 24 0 1 0; +#X connect 25 0 19 0; +#X connect 25 1 26 0; +#X connect 26 0 23 1; +#X connect 27 0 28 0; +#X connect 28 0 29 0; +#X connect 28 1 37 0; +#X connect 29 0 35 0; +#X connect 29 1 33 0; +#X connect 30 0 31 0; +#X connect 31 0 36 1; +#X connect 32 0 36 1; +#X connect 33 0 34 0; +#X connect 34 0 36 0; +#X connect 35 0 36 0; +#X connect 36 0 47 0; +#X connect 37 0 32 0; +#X connect 37 1 30 0; +#X connect 38 0 43 0; +#X connect 39 0 40 0; +#X connect 40 0 42 0; +#X connect 41 0 42 0; +#X connect 42 0 48 0; +#X connect 43 0 45 0; +#X connect 43 1 44 0; +#X connect 44 0 42 1; +#X connect 45 0 41 0; +#X connect 45 1 39 0; +#X connect 46 0 38 0; +#X connect 46 1 27 0; +#X connect 46 2 5 0; +#X connect 46 3 18 0; +#X connect 47 0 1 0; +#X connect 48 0 1 0;