g_readwrite.c 33.5 KB
Newer Older
Miller Puckette's avatar
Miller Puckette committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
/* Copyright (c) 1997-2002 Miller Puckette and others.
* For information on usage and redistribution, and for a DISCLAIMER OF ALL
* WARRANTIES, see the file, "LICENSE.txt," in this distribution.  */

/* 
Routines to read and write canvases to files:
canvas_savetofile() writes a root canvas to a "pd" file.  (Reading "pd" files
is done simply by passing the contents to the pd message interpreter.)
Alternatively, the  glist_read() and glist_write() routines read and write
"data" from and to files (reading reads into an existing canvas), using a
file format as in the dialog window for data.
*/

#include <stdlib.h>
#include <stdio.h>
#include "m_pd.h"
#include "g_canvas.h"
#include <string.h>

void canvas_savedeclarationsto(t_canvas *x, t_binbuf *b);

    /* the following routines read "scalars" from a file into a canvas. */

static int canvas_scanbinbuf(int natoms, t_atom *vec, int *p_indexout,
    int *p_next)
{
Mathieu L Bouchard's avatar
Mathieu L Bouchard committed
27
    int i;
Miller Puckette's avatar
Miller Puckette committed
28 29 30 31 32 33 34 35 36 37 38 39
    int indexwas = *p_next;
    *p_indexout = indexwas;
    if (indexwas >= natoms)
        return (0);
    for (i = indexwas; i < natoms && vec[i].a_type != A_SEMI; i++)
        ;
    if (i >= natoms)
        *p_next = i;
    else *p_next = i + 1;
    return (i - indexwas);
}

40 41 42
int canvas_readscalar(t_glist *x, int natoms, t_atom *vec,
     int *p_nextmsg, int selectit);

Miller Puckette's avatar
Miller Puckette committed
43 44 45 46

static void canvas_readerror(int natoms, t_atom *vec, int message, 
    int nline, char *s)
{
Ivica Ico Bukvic's avatar
Ivica Ico Bukvic committed
47
    error("%s", s);
Miller Puckette's avatar
Miller Puckette committed
48 49 50 51 52 53 54 55 56 57
    startpost("line was:");
    postatom(nline, vec + message);
    endpost();
}

    /* fill in the contents of the scalar into the vector w. */

static void glist_readatoms(t_glist *x, int natoms, t_atom *vec,
    int *p_nextmsg, t_symbol *templatesym, t_word *w, int argc, t_atom *argv)
{
Mathieu L Bouchard's avatar
Mathieu L Bouchard committed
58
    int message, n, i;
Miller Puckette's avatar
Miller Puckette committed
59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74

    t_template *template = template_findbyname(templatesym);
    if (!template)
    {
        error("%s: no such template", templatesym->s_name);
        *p_nextmsg = natoms;
        return;
    }
    word_restore(w, template, argc, argv);
    n = template->t_n;
    for (i = 0; i < n; i++)
    {
        if (template->t_vec[i].ds_type == DT_ARRAY)
        {
            t_array *a = w[i].w_array;
            int elemsize = a->a_elemsize, nitems = 0;
75
            t_symbol *arraytemplatesym = template->t_vec[i].ds_fieldtemplate;
Miller Puckette's avatar
Miller Puckette committed
76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98
            t_template *arraytemplate =
                template_findbyname(arraytemplatesym);
            if (!arraytemplate)
            {
                error("%s: no such template", arraytemplatesym->s_name);
            }
            else while (1)
            {
                t_word *element;
                int nline = canvas_scanbinbuf(natoms, vec, &message, p_nextmsg);
                    /* empty line terminates array */
                if (!nline)
                    break;
                array_resize(a, nitems + 1);
                element = (t_word *)(((char *)a->a_vec) +
                    nitems * elemsize);
                glist_readatoms(x, natoms, vec, p_nextmsg, arraytemplatesym,
                    element, nline, vec + message);
                nitems++;
            }
        }
        else if (template->t_vec[i].ds_type == DT_LIST)
        {
99 100 101 102 103 104 105 106 107 108 109 110 111 112 113
            /* nothing needs to happen here */
        }
        else if (template->t_vec[i].ds_type == DT_TEXT)
        {
            // Miller's addition for the [text] object
            t_binbuf *z = binbuf_new();
            int first = *p_nextmsg, last;
            for (last = first; last < natoms && vec[last].a_type != A_SEMI;
                last++);
            binbuf_restore(z, last-first, vec+first);
            binbuf_add(w[i].w_binbuf, binbuf_getnatom(z), binbuf_getvec(z));
            binbuf_free(z);
            last++;
            if (last > natoms) last = natoms;
            *p_nextmsg = last;
Miller Puckette's avatar
Miller Puckette committed
114 115 116 117
        }
    }
}

Jonathan Wilkes's avatar
Jonathan Wilkes committed
118 119
void scalar_doloadbang(t_scalar *x);

120
int canvas_readscalar(t_glist *x, int natoms, t_atom *vec,
Miller Puckette's avatar
Miller Puckette committed
121 122
    int *p_nextmsg, int selectit)
{
Mathieu L Bouchard's avatar
Mathieu L Bouchard committed
123
    int message, nline;
Miller Puckette's avatar
Miller Puckette committed
124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172
    t_template *template;
    t_symbol *templatesym;
    t_scalar *sc;
    int nextmsg = *p_nextmsg;
    int wasvis = glist_isvisible(x);

    if (nextmsg >= natoms || vec[nextmsg].a_type != A_SYMBOL)
    {
        if (nextmsg < natoms)
            post("stopping early: type %d", vec[nextmsg].a_type);
        *p_nextmsg = natoms;
        return (0);
    }
    templatesym = canvas_makebindsym(vec[nextmsg].a_w.w_symbol);
    *p_nextmsg = nextmsg + 1;
    
    if (!(template = template_findbyname(templatesym)))
    {
        error("canvas_read: %s: no such template", templatesym->s_name);
        *p_nextmsg = natoms;
        return (0);
    }
    sc = scalar_new(x, templatesym);
    if (!sc)
    {
        error("couldn't create scalar \"%s\"", templatesym->s_name);
        *p_nextmsg = natoms;
        return (0);
    }
    if (wasvis)
    {
            /* temporarily lie about vis flag while this is built */
        glist_getcanvas(x)->gl_mapped = 0;
    }
    glist_add(x, &sc->sc_gobj);
    
    nline = canvas_scanbinbuf(natoms, vec, &message, p_nextmsg);
    glist_readatoms(x, natoms, vec, p_nextmsg, templatesym, sc->sc_vec, 
        nline, vec + message);
    if (wasvis)
    {
            /* reset vis flag as before */
        glist_getcanvas(x)->gl_mapped = 1;
        gobj_vis(&sc->sc_gobj, x, 1);
    }
    if (selectit)
    {
        glist_select(x, &sc->sc_gobj);
    }
Jonathan Wilkes's avatar
Jonathan Wilkes committed
173 174
    /* send a loadbang for any canvas fields in this scalar */
    scalar_doloadbang(sc);
Miller Puckette's avatar
Miller Puckette committed
175 176 177 178 179
    return (1);
}

void glist_readfrombinbuf(t_glist *x, t_binbuf *b, char *filename, int selectem)
{
Jonathan Wilkes's avatar
Jonathan Wilkes committed
180
    int natoms, nline, message, nextmsg = 0;
Miller Puckette's avatar
Miller Puckette committed
181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203
    t_atom *vec;

    natoms = binbuf_getnatom(b);
    vec = binbuf_getvec(b);

    
            /* check for file type */
    nline = canvas_scanbinbuf(natoms, vec, &message, &nextmsg);
    if (nline != 1 && vec[message].a_type != A_SYMBOL &&
        strcmp(vec[message].a_w.w_symbol->s_name, "data"))
    {
        pd_error(x, "%s: file apparently of wrong type", filename);
        return;
    }
        /* read in templates and check for consistency */
    while (1)
    {
        t_template *newtemplate, *existtemplate;
        t_symbol *templatesym;
        t_atom *templateargs = getbytes(0);
        int ntemplateargs = 0, newnargs;
        nline = canvas_scanbinbuf(natoms, vec, &message, &nextmsg);
        if (nline < 2)
204 205
        {
            t_freebytes(templateargs, sizeof(*templateargs) * ntemplateargs);
Miller Puckette's avatar
Miller Puckette committed
206
            break;
207
        }
Miller Puckette's avatar
Miller Puckette committed
208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238
        else if (nline > 2)
            canvas_readerror(natoms, vec, message, nline,
                "extra items ignored");
        else if (vec[message].a_type != A_SYMBOL ||
            strcmp(vec[message].a_w.w_symbol->s_name, "template") ||
            vec[message + 1].a_type != A_SYMBOL)
        {
            canvas_readerror(natoms, vec, message, nline,
                "bad template header");
            continue;
        }
        templatesym = canvas_makebindsym(vec[message + 1].a_w.w_symbol);
        while (1)
        {
            nline = canvas_scanbinbuf(natoms, vec, &message, &nextmsg);
            if (nline != 2 && nline != 3)
                break;
            newnargs = ntemplateargs + nline;
            templateargs = (t_atom *)t_resizebytes(templateargs,
                sizeof(*templateargs) * ntemplateargs,
                sizeof(*templateargs) * newnargs);
            templateargs[ntemplateargs] = vec[message];
            templateargs[ntemplateargs + 1] = vec[message + 1];
            if (nline == 3)
                templateargs[ntemplateargs + 2] = vec[message + 2];
            ntemplateargs = newnargs;
        }
        if (!(existtemplate = template_findbyname(templatesym)))
        {
            error("%s: template not found in current patch",
                templatesym->s_name);
239
            t_freebytes(templateargs, sizeof (*templateargs) * ntemplateargs);
Miller Puckette's avatar
Miller Puckette committed
240 241
            return;
        }
242 243
        newtemplate = template_new(templatesym, ntemplateargs, templateargs);
        t_freebytes(templateargs, sizeof (*templateargs) * ntemplateargs);
Miller Puckette's avatar
Miller Puckette committed
244 245 246 247
        if (!template_match(existtemplate, newtemplate))
        {
            error("%s: template doesn't match current one",
                templatesym->s_name);
248
            pd_free(&newtemplate->t_pdobj);
Miller Puckette's avatar
Miller Puckette committed
249 250
            return;
        }
251
        pd_free(&newtemplate->t_pdobj);
Miller Puckette's avatar
Miller Puckette committed
252 253 254
    }
    while (nextmsg < natoms)
    {
255
        canvas_readscalar(x, natoms, vec, &nextmsg, selectem);
Miller Puckette's avatar
Miller Puckette committed
256 257 258 259 260 261 262 263 264
    }
}

static void glist_doread(t_glist *x, t_symbol *filename, t_symbol *format,
    int clearme)
{
    t_binbuf *b = binbuf_new();
    t_canvas *canvas = glist_getcanvas(x);
    int wasvis = glist_isvisible(canvas);
Mathieu L Bouchard's avatar
Mathieu L Bouchard committed
265
    int cr = 0;
Miller Puckette's avatar
Miller Puckette committed
266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304

    if (!strcmp(format->s_name, "cr"))
        cr = 1;
    else if (*format->s_name)
        error("qlist_read: unknown flag: %s", format->s_name);
    
    if (binbuf_read_via_canvas(b, filename->s_name, canvas, cr))
    {
        pd_error(x, "read failed");
        binbuf_free(b);
        return;
    }
    if (wasvis)
        canvas_vis(canvas, 0);
    if (clearme)
        glist_clear(x);
    glist_readfrombinbuf(x, b, filename->s_name, 0);
    if (wasvis)
        canvas_vis(canvas, 1);
    binbuf_free(b);
}

void glist_read(t_glist *x, t_symbol *filename, t_symbol *format)
{
    glist_doread(x, filename, format, 1);
}

void glist_mergefile(t_glist *x, t_symbol *filename, t_symbol *format)
{
    glist_doread(x, filename, format, 0);
}

    /* read text from a "properties" window, called from a gfxstub set
    up in scalar_properties().  We try to restore the object; if successful
    we delete the scalar and put the new thing in its place on the list. */
void canvas_dataproperties(t_canvas *x, t_scalar *sc, t_binbuf *b)
{
    int ntotal, nnew, scindex;
    t_gobj *y, *y2 = 0, *newone, *oldone = 0;
305
    t_template *template;
Miller Puckette's avatar
Miller Puckette committed
306 307 308 309 310 311 312 313
    for (y = x->gl_list, ntotal = 0, scindex = -1; y; y = y->g_next)
    {
        if (y == &sc->sc_gobj)
            scindex = ntotal, oldone = y;
        ntotal++;
    }
    
    if (scindex == -1)
314 315 316 317
    {
        error("data_properties: scalar disappeared");
        return;
    }
Miller Puckette's avatar
Miller Puckette committed
318 319 320
    glist_readfrombinbuf(x, b, "properties dialog", 0);
    newone = 0;
        /* take the new object off the list */
321 322 323 324 325
    if (ntotal)
    {
        for (y = x->gl_list, nnew = 1; y2 = y->g_next;
            y = y2, nnew++)
                if (nnew == ntotal)
Miller Puckette's avatar
Miller Puckette committed
326
        {
327 328 329 330
            newone = y2;
            gobj_vis(newone, x, 0);
            y->g_next = y2->g_next;
            break;    
Miller Puckette's avatar
Miller Puckette committed
331 332
        }
    }
333
    else gobj_vis((newone = x->gl_list), x, 0), x->gl_list = newone->g_next;
Miller Puckette's avatar
Miller Puckette committed
334 335 336 337
    if (!newone)
        error("couldn't update properties (perhaps a format problem?)");
    else if (!oldone)
        bug("data_properties: couldn't find old element");
338 339 340 341 342 343
    else if (newone->g_pd == scalar_class && oldone->g_pd == scalar_class
        && ((t_scalar *)newone)->sc_template ==
            ((t_scalar *)oldone)->sc_template 
        && (template = template_findbyname(((t_scalar *)newone)->sc_template)))
    {
            /* copy new one to old one and delete new one */
344 345 346 347 348 349 350 351 352 353 354
        int i;
        /* swap out the sc_vec field. That way we'll keep the one from
           the new scalar, and the old one will get freed by word_free (which
           gets called from pd_free below)
        */
        for (i = 0; i < template->t_n; i++)
        {
            t_word w = ((t_scalar *)newone)->sc_vec[i];
            ((t_scalar *)newone)->sc_vec[i] = ((t_scalar *)oldone)->sc_vec[i];
            ((t_scalar *)oldone)->sc_vec[i] = w;
        }
355 356 357 358 359 360 361
        pd_free(&newone->g_pd);
        if (glist_isvisible(x))
        {
            gobj_vis(oldone, x, 0);
            gobj_vis(oldone, x, 1);
        }
    }
Miller Puckette's avatar
Miller Puckette committed
362 363
    else
    {
364
            /* delete old one; put new one where the old one was on glist */
Miller Puckette's avatar
Miller Puckette committed
365 366 367 368 369 370 371 372 373 374 375 376 377 378 379
        glist_delete(x, oldone);
        if (scindex > 0)
        {
            for (y = x->gl_list, nnew = 1; y;
                y = y->g_next, nnew++)
                    if (nnew == scindex || !y->g_next)
            {
                newone->g_next = y->g_next;
                y->g_next = newone;
                goto didit;
            }
            bug("data_properties: can't reinsert");
        }
        else newone->g_next = x->gl_list, x->gl_list = newone;
    }
380 381
    // here we check for changes in scrollbar due to potential repositioning
    canvas_getscroll(x);
Miller Puckette's avatar
Miller Puckette committed
382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445
didit:
    ;
}

    /* ----------- routines to write data to a binbuf ----------- */

void canvas_doaddtemplate(t_symbol *templatesym, 
    int *p_ntemplates, t_symbol ***p_templatevec)
{
    int n = *p_ntemplates, i;
    t_symbol **templatevec = *p_templatevec;
    for (i = 0; i < n; i++)
        if (templatevec[i] == templatesym)
            return;
    templatevec = (t_symbol **)t_resizebytes(templatevec,
        n * sizeof(*templatevec), (n+1) * sizeof(*templatevec));
    templatevec[n] = templatesym;
    *p_templatevec = templatevec;
    *p_ntemplates = n+1;
}

static void glist_writelist(t_gobj *y, t_binbuf *b);

void canvas_writescalar(t_symbol *templatesym, t_word *w, t_binbuf *b,
    int amarrayelement)
{
    t_template *template = template_findbyname(templatesym);
    t_atom *a = (t_atom *)t_getbytes(0);
    int i, n = template->t_n, natom = 0;
    if (!amarrayelement)
    {
        t_atom templatename;
        SETSYMBOL(&templatename, gensym(templatesym->s_name + 3));
        binbuf_add(b, 1, &templatename);
    }
    if (!template)
        bug("canvas_writescalar");
        /* write the atoms (floats and symbols) */
    for (i = 0; i < n; i++)
    {
        if (template->t_vec[i].ds_type == DT_FLOAT ||
            template->t_vec[i].ds_type == DT_SYMBOL)
        {
            a = (t_atom *)t_resizebytes(a,
                natom * sizeof(*a), (natom + 1) * sizeof (*a));
            if (template->t_vec[i].ds_type == DT_FLOAT)
                SETFLOAT(a + natom, w[i].w_float);
            else SETSYMBOL(a + natom,  w[i].w_symbol);
            natom++;
        }
    }
        /* array elements have to have at least something */
    if (natom == 0 && amarrayelement)
        SETSYMBOL(a + natom,  &s_bang), natom++;
    binbuf_add(b, natom, a);
    binbuf_addsemi(b);
    t_freebytes(a, natom * sizeof(*a));
    for (i = 0; i < n; i++)
    {
        if (template->t_vec[i].ds_type == DT_ARRAY)
        {
            int j;
            t_array *a = w[i].w_array;
            int elemsize = a->a_elemsize, nitems = a->a_n;
446
            t_symbol *arraytemplatesym = template->t_vec[i].ds_fieldtemplate;
Miller Puckette's avatar
Miller Puckette committed
447 448 449 450 451 452 453
            for (j = 0; j < nitems; j++)
                canvas_writescalar(arraytemplatesym,
                    (t_word *)(((char *)a->a_vec) + elemsize * j), b, 1);
            binbuf_addsemi(b);
        }
        else if (template->t_vec[i].ds_type == DT_LIST)
        {
454 455 456
            /* This needs to be revisited if we want to keep the
               canvas field API */
            //glist_writelist(w->w_list->gl_list, b);
Miller Puckette's avatar
Miller Puckette committed
457 458
            binbuf_addsemi(b);
        }
459 460 461 462 463
        else if (template->t_vec[i].ds_type == DT_TEXT)
        {
            // Miller's addition for the implementation of the [text] object
            binbuf_savetext(w[i].w_binbuf, b);
        }
Miller Puckette's avatar
Miller Puckette committed
464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499
    }
}

static void glist_writelist(t_gobj *y, t_binbuf *b)
{
    for (; y; y = y->g_next)
    {
        if (pd_class(&y->g_pd) == scalar_class)
        {
            canvas_writescalar(((t_scalar *)y)->sc_template,
                ((t_scalar *)y)->sc_vec, b, 0);
        }
    }
}

    /* ------------ routines to write out templates for data ------- */

static void canvas_addtemplatesforlist(t_gobj *y,
    int  *p_ntemplates, t_symbol ***p_templatevec);

static void canvas_addtemplatesforscalar(t_symbol *templatesym,
    t_word *w, int *p_ntemplates, t_symbol ***p_templatevec)
{
    t_dataslot *ds;
    int i;
    t_template *template = template_findbyname(templatesym);
    canvas_doaddtemplate(templatesym, p_ntemplates, p_templatevec);
    if (!template)
        bug("canvas_addtemplatesforscalar");
    else for (ds = template->t_vec, i = template->t_n; i--; ds++, w++)
    {
        if (ds->ds_type == DT_ARRAY)
        {
            int j;
            t_array *a = w->w_array;
            int elemsize = a->a_elemsize, nitems = a->a_n;
500
            t_symbol *arraytemplatesym = ds->ds_fieldtemplate;
Miller Puckette's avatar
Miller Puckette committed
501 502 503 504 505 506 507
            canvas_doaddtemplate(arraytemplatesym, p_ntemplates, p_templatevec);
            for (j = 0; j < nitems; j++)
                canvas_addtemplatesforscalar(arraytemplatesym,
                    (t_word *)(((char *)a->a_vec) + elemsize * j), 
                        p_ntemplates, p_templatevec);
        }
        else if (ds->ds_type == DT_LIST)
508
        {
509 510 511 512
            /* This needs to be revisited if we want to keep the
               canvas field list */
            //canvas_addtemplatesforlist(w->w_list->gl_list,
            //    p_ntemplates, p_templatevec);
513
        }
514 515
        else if (ds->ds_type == DT_TEXT)
        {
Jonathan Wilkes's avatar
Jonathan Wilkes committed
516 517
            //canvas_addtemplatesforlist(w->w_list->gl_list,
            //    p_ntemplates, p_templatevec);
518
        }
Miller Puckette's avatar
Miller Puckette committed
519 520 521
    }
}

522 523 524 525 526 527 528 529 530 531 532 533
static void canvas_addtemplatesforstruct(t_template *template,
    int *p_ntemplates, t_symbol ***p_templatevec)
{
    t_dataslot *ds;
    int i;
    canvas_doaddtemplate(template->t_sym, p_ntemplates, p_templatevec);
    if (!template)
        bug("canvas_addtemplatesforscalar");
    else for (ds = template->t_vec, i = template->t_n; i--; ds++)
    {
        if (ds->ds_type == DT_ARRAY)
        {
534
            t_symbol *arraytemplatesym = ds->ds_fieldtemplate;
535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551
            t_template *arraytemplate = template_findbyname(arraytemplatesym);
            if (arraytemplate)
            {
                canvas_doaddtemplate(arraytemplatesym, p_ntemplates,
                    p_templatevec);
                canvas_addtemplatesforstruct(arraytemplate,
                        p_ntemplates, p_templatevec);
            }
        }
        /* not sure about this part-- all the sublist datatype seems
           to do is crash */
        //else if (ds->ds_type == DT_LIST)
        //    canvas_addtemplatesforlist(w->w_list->gl_list,
        //        p_ntemplates, p_templatevec);
    }
}

Miller Puckette's avatar
Miller Puckette committed
552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598
static void canvas_addtemplatesforlist(t_gobj *y,
    int  *p_ntemplates, t_symbol ***p_templatevec)
{
    for (; y; y = y->g_next)
    {
        if (pd_class(&y->g_pd) == scalar_class)
        {
            canvas_addtemplatesforscalar(((t_scalar *)y)->sc_template,
                ((t_scalar *)y)->sc_vec, p_ntemplates, p_templatevec);
        }
    }
}

    /* write all "scalars" in a glist to a binbuf. */
t_binbuf *glist_writetobinbuf(t_glist *x, int wholething)
{
    int i;
    t_symbol **templatevec = getbytes(0);
    int ntemplates = 0;
    t_gobj *y;
    t_binbuf *b = binbuf_new();

    for (y = x->gl_list; y; y = y->g_next)
    {
        if ((pd_class(&y->g_pd) == scalar_class) &&
            (wholething || glist_isselected(x, y)))
        {
            canvas_addtemplatesforscalar(((t_scalar *)y)->sc_template,
                ((t_scalar *)y)->sc_vec,  &ntemplates, &templatevec);
        }
    }
    binbuf_addv(b, "s;", gensym("data"));
    for (i = 0; i < ntemplates; i++)
    {
        t_template *template = template_findbyname(templatevec[i]);
        int j, m = template->t_n;
            /* drop "pd-" prefix from template symbol to print it: */
        binbuf_addv(b, "ss;", gensym("template"),
            gensym(templatevec[i]->s_name + 3));
        for (j = 0; j < m; j++)
        {
            t_symbol *type;
            switch (template->t_vec[j].ds_type)
            {
                case DT_FLOAT: type = &s_float; break;
                case DT_SYMBOL: type = &s_symbol; break;
                case DT_ARRAY: type = gensym("array"); break;
599 600
                case DT_LIST: type = gensym("canvas"); break;
                case DT_TEXT: type = &s_list; break;
Miller Puckette's avatar
Miller Puckette committed
601 602
                default: type = &s_float; bug("canvas_write");
            }
603 604
            if (template->t_vec[j].ds_type == DT_ARRAY ||
                template->t_vec[j].ds_type == DT_LIST)
Miller Puckette's avatar
Miller Puckette committed
605
                binbuf_addv(b, "sss;", type, template->t_vec[j].ds_name,
606
                    gensym(template->t_vec[j].ds_fieldtemplate->s_name + 3));
Miller Puckette's avatar
Miller Puckette committed
607 608 609 610 611 612 613 614 615 616 617 618 619 620 621
            else binbuf_addv(b, "ss;", type, template->t_vec[j].ds_name);
        }
        binbuf_addsemi(b);
    }
    binbuf_addsemi(b);
        /* now write out the objects themselves */
    for (y = x->gl_list; y; y = y->g_next)
    {
        if ((pd_class(&y->g_pd) == scalar_class) &&
            (wholething || glist_isselected(x, y)))
        {
            canvas_writescalar(((t_scalar *)y)->sc_template,
                ((t_scalar *)y)->sc_vec,  b, 0);
        }
    }
622
    t_freebytes(templatevec, ntemplates * sizeof(*templatevec));
Miller Puckette's avatar
Miller Puckette committed
623 624 625 626 627
    return (b);
}

static void glist_write(t_glist *x, t_symbol *filename, t_symbol *format)
{
Mathieu L Bouchard's avatar
Mathieu L Bouchard committed
628
    int cr = 0;
Miller Puckette's avatar
Miller Puckette committed
629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648
    t_binbuf *b;
    char buf[MAXPDSTRING];
    t_canvas *canvas = glist_getcanvas(x);
    canvas_makefilename(canvas, filename->s_name, buf, MAXPDSTRING);
    if (!strcmp(format->s_name, "cr"))
        cr = 1;
    else if (*format->s_name)
        error("qlist_read: unknown flag: %s", format->s_name);
    
    b = glist_writetobinbuf(x, 1);
    if (b)
    {
        if (binbuf_write(b, buf, "", cr))
            error("%s: write failed", filename->s_name);
        binbuf_free(b);
    }
}

/* ------ routines to save and restore canvases (patches) recursively. ----*/

649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670
static double zoom_hack(int x, int zoom)
{
  // AG: This employs an interesting little hack made possible by a loophole
  // in Pd's patch parser (binbuf_eval), which will happily read a float value
  // where an int is expected, and cast it to an int anyway. Applied to the #N
  // header of a canvas, this lets us store the zoom level of a patch in the
  // fractional part of a window geometry argument x, so that we can restore
  // it when the patch is reloaded. Since vanilla and other Pd flavors won't
  // even notice the extra data, the same patch can still be loaded by any
  // other Pd version without any problems.

  // Encoding of the zoom level: Purr Data's zoom levels go from -7 to 8 so
  // that we can encode them as 4 bit numbers in two's complement. However, it
  // makes sense to leave 1 extra bit for future extensions, so our encoding
  // actually uses nonnegative 5 bit integer multiples of 2^-5 = 0.03125 in a
  // way that leaves the integer part of the height parameter intact. Note
  // that using 2's complement here has the advantage that a zero zoom level
  // maps to a zero fraction so that we don't need an extra bit to detect
  // whether there's a zoom level when reading the patch.
  return x + (zoom & 31) * 0.03125;
}

Miller Puckette's avatar
Miller Puckette committed
671 672 673 674 675 676 677
    /* save to a binbuf, called recursively; cf. canvas_savetofile() which
    saves the document, and is only called on root canvases. */
static void canvas_saveto(t_canvas *x, t_binbuf *b)
{
    t_gobj *y;
    t_linetraverser t;
    t_outconnect *oc;
678
    extern int sys_zoom;
Miller Puckette's avatar
Miller Puckette committed
679 680 681 682
        /* subpatch */
    if (x->gl_owner && !x->gl_env)
    {
        /* have to go to original binbuf to find out how we were named. */
683
        //fprintf(stderr,"saving subpatch\n");
Miller Puckette's avatar
Miller Puckette committed
684
        t_binbuf *bz = binbuf_new();
685 686
        t_symbol *patchsym, *selector;
        int name_index;
Miller Puckette's avatar
Miller Puckette committed
687
        binbuf_addbinbuf(bz, x->gl_obj.ob_binbuf);
688 689 690 691 692 693 694 695
        selector = atom_getsymbolarg(0, binbuf_getnatom(bz), binbuf_getvec(bz));
        /* For [draw group] we save the name as the third argument. This
           is rather obscure but it might be handy if people want to
           dynamically create objects inside a [draw group] */
        if (selector == gensym("draw")) name_index = 2;
        else name_index = 1;
        patchsym = atom_getsymbolarg(name_index,
            binbuf_getnatom(bz), binbuf_getvec(bz));
Miller Puckette's avatar
Miller Puckette committed
696
        binbuf_free(bz);
697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721
        if (sys_zoom && x->gl_zoom != 0) {
          // This uses the hack described above to store the zoom factor in
          // the fractional part of the windows height parameter. Note that
          // any of the other canvas geometry parameters would do just as
          // well, as they are all measured in pixels and thus unlikely to
          // become fractional in the future.
          binbuf_addv(b, "ssiiifsi;", gensym("#N"), gensym("canvas"),
                      (int)(x->gl_screenx1),
                      (int)(x->gl_screeny1),
                      (int)(x->gl_screenx2 - x->gl_screenx1),
                      zoom_hack(x->gl_screeny2 - x->gl_screeny1, x->gl_zoom),
                      (patchsym != &s_ ? patchsym: gensym("(subpatch)")),
                      x->gl_mapped);
        } else {
          // This is the standard (vanilla) code. This is also used in the
          // case of a zero zoom level, so unzoomed patches will always
          // produce a standard vanilla patch file when saved.
          binbuf_addv(b, "ssiiiisi;", gensym("#N"), gensym("canvas"),
                      (int)(x->gl_screenx1),
                      (int)(x->gl_screeny1),
                      (int)(x->gl_screenx2 - x->gl_screenx1),
                      (int)(x->gl_screeny2 - x->gl_screeny1),
                      (patchsym != &s_ ? patchsym: gensym("(subpatch)")),
                      x->gl_mapped);
        }
Jonathan Wilkes's avatar
Jonathan Wilkes committed
722 723
        /* Not sure what the following commented line was doing... */
        //t_text *xt = (t_text *)x;
Miller Puckette's avatar
Miller Puckette committed
724 725 726 727
    }
        /* root or abstraction */
    else 
    {
728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743
        // See above.
        if (sys_zoom && x->gl_zoom != 0) {
          binbuf_addv(b, "ssiiifi;", gensym("#N"), gensym("canvas"),
                      (int)(x->gl_screenx1),
                      (int)(x->gl_screeny1),
                      (int)(x->gl_screenx2 - x->gl_screenx1),
                      zoom_hack(x->gl_screeny2 - x->gl_screeny1, x->gl_zoom),
                      (int)x->gl_font);
        } else {
          binbuf_addv(b, "ssiiiii;", gensym("#N"), gensym("canvas"),
                      (int)(x->gl_screenx1),
                      (int)(x->gl_screeny1),
                      (int)(x->gl_screenx2 - x->gl_screenx1),
                      (int)(x->gl_screeny2 - x->gl_screeny1),
                      (int)x->gl_font);
        }
Miller Puckette's avatar
Miller Puckette committed
744 745 746 747 748 749 750 751
        canvas_savedeclarationsto(x, b);
    }
    for (y = x->gl_list; y; y = y->g_next)
        gobj_save(y, b);

    linetraverser_start(&t, x);
    while (oc = linetraverser_next(&t))
    {
752 753 754 755 756 757 758
        if (outconnect_visible(oc))
        {
            int srcno = canvas_getindex(x, &t.tr_ob->ob_g);
            int sinkno = canvas_getindex(x, &t.tr_ob2->ob_g);
            binbuf_addv(b, "ssiiii;", gensym("#X"), gensym("connect"),
                srcno, t.tr_outno, sinkno, t.tr_inno);
        }
Miller Puckette's avatar
Miller Puckette committed
759 760 761 762 763 764 765 766 767 768 769 770 771
    }
        /* unless everything is the default (as in ordinary subpatches)
        print out a "coords" message to set up the coordinate systems */
    if (x->gl_isgraph || x->gl_x1 || x->gl_y1 ||
        x->gl_x2 != 1 ||  x->gl_y2 != 1 || x->gl_pixwidth || x->gl_pixheight)
    {
        if (x->gl_isgraph && x->gl_goprect)
                /* if we have a graph-on-parent rectangle, we're new style.
                The format is arranged so
                that old versions of Pd can at least do something with it. */
            binbuf_addv(b, "ssfffffffff;", gensym("#X"), gensym("coords"),
                x->gl_x1, x->gl_y1,
                x->gl_x2, x->gl_y2,
772 773 774
                (t_float)x->gl_pixwidth, (t_float)x->gl_pixheight,
                (t_float)((x->gl_hidetext)?2.:1.),
                (t_float)x->gl_xmargin, (t_float)x->gl_ymargin); 
Miller Puckette's avatar
Miller Puckette committed
775 776 777 778
                    /* otherwise write in 0.38-compatible form */
        else binbuf_addv(b, "ssfffffff;", gensym("#X"), gensym("coords"),
                x->gl_x1, x->gl_y1,
                x->gl_x2, x->gl_y2,
779 780
                (t_float)x->gl_pixwidth, (t_float)x->gl_pixheight,
                (t_float)x->gl_isgraph);
Miller Puckette's avatar
Miller Puckette committed
781
    }
782 783 784 785 786 787 788
        /* save a message if scrollbars are disabled-- otherwise do nothing
           for the sake of backwards compatibility. */
    if (x->gl_noscroll)
        binbuf_addv(b, "ssi;", gensym("#X"), gensym("scroll"), x->gl_noscroll);
        /* same for menu */
    if (x->gl_nomenu)
        binbuf_addv(b, "ssi;", gensym("#X"), gensym("menu"), x->gl_nomenu);
Miller Puckette's avatar
Miller Puckette committed
789 790
}

791 792 793
/* yuck, wish I didn't have to do this... */
extern t_class *gtemplate_class;

Miller Puckette's avatar
Miller Puckette committed
794 795 796 797 798 799 800 801 802 803 804 805 806
    /* call this recursively to collect all the template names for
    a canvas or for the selection. */
static void canvas_collecttemplatesfor(t_canvas *x, int *ntemplatesp,
    t_symbol ***templatevecp, int wholething)
{
    t_gobj *y;

    for (y = x->gl_list; y; y = y->g_next)
    {
        if ((pd_class(&y->g_pd) == scalar_class) &&
            (wholething || glist_isselected(x, y)))
                canvas_addtemplatesforscalar(((t_scalar *)y)->sc_template,
                    ((t_scalar *)y)->sc_vec,  ntemplatesp, templatevecp);
807 808 809 810
        else if ((pd_class(&y->g_pd) == gtemplate_class) &&
            (wholething || glist_isselected(x, y)))
                canvas_addtemplatesforstruct(gtemplate_get((t_gtemplate *)y),
                    ntemplatesp, templatevecp);
Miller Puckette's avatar
Miller Puckette committed
811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843
        else if ((pd_class(&y->g_pd) == canvas_class) &&
            (wholething || glist_isselected(x, y)))
                canvas_collecttemplatesfor((t_canvas *)y,
                    ntemplatesp, templatevecp, 1);
    }
}

    /* save the templates needed by a canvas to a binbuf. */
static void canvas_savetemplatesto(t_canvas *x, t_binbuf *b, int wholething)
{
    t_symbol **templatevec = getbytes(0);
    int i, ntemplates = 0;
    canvas_collecttemplatesfor(x, &ntemplates, &templatevec, wholething);
    for (i = 0; i < ntemplates; i++)
    {
        t_template *template = template_findbyname(templatevec[i]);
        int j, m = template->t_n;
        if (!template)
        {
            bug("canvas_savetemplatesto");
            continue;
        }
            /* drop "pd-" prefix from template symbol to print */
        binbuf_addv(b, "sss", &s__N, gensym("struct"),
            gensym(templatevec[i]->s_name + 3));
        for (j = 0; j < m; j++)
        {
            t_symbol *type;
            switch (template->t_vec[j].ds_type)
            {
                case DT_FLOAT: type = &s_float; break;
                case DT_SYMBOL: type = &s_symbol; break;
                case DT_ARRAY: type = gensym("array"); break;
844 845
                case DT_LIST: type = gensym("canvas"); break;
                case DT_TEXT: type = gensym("text"); break; //&s_list; break;
Miller Puckette's avatar
Miller Puckette committed
846 847
                default: type = &s_float; bug("canvas_write");
            }
848 849 850 851
            if (template->t_vec[j].ds_type == DT_LIST)
                binbuf_addv(b, "sss", type, template->t_vec[j].ds_name,
                    gensym(template->t_vec[j].ds_fieldtemplate->s_name));
            else if (template->t_vec[j].ds_type == DT_ARRAY)
Miller Puckette's avatar
Miller Puckette committed
852
                binbuf_addv(b, "sss", type, template->t_vec[j].ds_name,
853
                    gensym(template->t_vec[j].ds_fieldtemplate->s_name + 3));
Miller Puckette's avatar
Miller Puckette committed
854 855 856 857 858 859 860
            else binbuf_addv(b, "ss", type, template->t_vec[j].ds_name);
        }
        binbuf_addsemi(b);
    }
}

void canvas_reload(t_symbol *name, t_symbol *dir, t_gobj *except);
861
extern void canvasgop_checksize(t_canvas *x);
Miller Puckette's avatar
Miller Puckette committed
862 863 864

    /* save a "root" canvas to a file; cf. canvas_saveto() which saves the
    body (and which is called recursively.) */
865 866
static void canvas_savetofile(t_canvas *x, t_symbol *filename, t_symbol *dir,
    t_floatarg fdestroy)
Miller Puckette's avatar
Miller Puckette committed
867 868 869 870 871 872 873 874 875
{
    t_binbuf *b = binbuf_new();
    canvas_savetemplatesto(x, b, 1);
    canvas_saveto(x, b);
    if (binbuf_write(b, filename->s_name, dir->s_name, 0)) sys_ouch();
    else
    {
            /* if not an abstraction, reset title bar and directory */ 
        if (!x->gl_owner)
Hans-Christoph Steiner's avatar
Hans-Christoph Steiner committed
876
        {
Miller Puckette's avatar
Miller Puckette committed
877
            canvas_rename(x, filename, dir);
Hans-Christoph Steiner's avatar
Hans-Christoph Steiner committed
878 879 880
            /* update window list in case Save As changed the window name */
            canvas_updatewindowlist(); 
        }
Miller Puckette's avatar
Miller Puckette committed
881 882
        post("saved to: %s/%s", dir->s_name, filename->s_name);
        canvas_dirty(x, 0);
883 884
        if (x->gl_isgraph)
            canvasgop_checksize(x);
Miller Puckette's avatar
Miller Puckette committed
885
        canvas_reload(filename, dir, &x->gl_gobj);
886 887
        if (fdestroy != 0)
            vmess(&x->gl_pd, gensym("menuclose"), "f", 1.);
Miller Puckette's avatar
Miller Puckette committed
888 889 890 891
    }
    binbuf_free(b);
}

892
static void canvas_menusaveas(t_canvas *x, t_floatarg fdestroy)
Miller Puckette's avatar
Miller Puckette committed
893 894
{
    t_canvas *x2 = canvas_getrootfor(x);
895 896 897 898 899 900
    gui_vmess("gui_canvas_saveas", "xssi",
        x2,
        (strncmp(x2->gl_name->s_name, "Untitled", 8) ?
            x2->gl_name->s_name : "title"),
        canvas_getdir(x2)->s_name,
        fdestroy != 0);
Miller Puckette's avatar
Miller Puckette committed
901 902
}

903
static void canvas_menusave(t_canvas *x, t_floatarg fdestroy)
Miller Puckette's avatar
Miller Puckette committed
904 905 906 907
{
    t_canvas *x2 = canvas_getrootfor(x);
    char *name = x2->gl_name->s_name;
    if (*name && strncmp(name, "Untitled", 8)
Hans-Christoph Steiner's avatar
Hans-Christoph Steiner committed
908 909
            && (strlen(name) < 4 || strcmp(name + strlen(name)-4, ".pat")
                || strcmp(name + strlen(name)-4, ".mxt")))
910 911
            canvas_savetofile(x2, x2->gl_name, canvas_getdir(x2), fdestroy);
    else canvas_menusaveas(x2, fdestroy);
Miller Puckette's avatar
Miller Puckette committed
912 913
}

914 915 916 917 918 919
static void canvas_menuprint(t_canvas *x)
{
    t_canvas *x2 = canvas_getrootfor(x);
    gui_vmess("gui_canvas_print", "xss", x, x->gl_name->s_name, canvas_getdir(x2)->s_name);
}

Miller Puckette's avatar
Miller Puckette committed
920 921 922 923 924 925 926 927 928
void g_readwrite_setup(void)
{
    class_addmethod(canvas_class, (t_method)glist_write,
        gensym("write"), A_SYMBOL, A_DEFSYM, A_NULL);
    class_addmethod(canvas_class, (t_method)glist_read,
        gensym("read"), A_SYMBOL, A_DEFSYM, A_NULL);
    class_addmethod(canvas_class, (t_method)glist_mergefile,
        gensym("mergefile"), A_SYMBOL, A_DEFSYM, A_NULL);
    class_addmethod(canvas_class, (t_method)canvas_savetofile,
929
        gensym("savetofile"), A_SYMBOL, A_SYMBOL, A_DEFFLOAT, 0);
Miller Puckette's avatar
Miller Puckette committed
930 931 932 933
    class_addmethod(canvas_class, (t_method)canvas_saveto,
        gensym("saveto"), A_CANT, 0);
/* ------------------ from the menu ------------------------- */
    class_addmethod(canvas_class, (t_method)canvas_menusave,
934
        gensym("menusave"), A_DEFFLOAT, 0);
Miller Puckette's avatar
Miller Puckette committed
935
    class_addmethod(canvas_class, (t_method)canvas_menusaveas,
936
        gensym("menusaveas"), A_DEFFLOAT, 0);
937 938
    class_addmethod(canvas_class, (t_method)canvas_menuprint,
        gensym("menuprint"), 0);
Miller Puckette's avatar
Miller Puckette committed
939
}
940 941 942 943 944 945 946

void canvas_readwrite_for_class(t_class *c)
{
    class_addmethod(c, (t_method)canvas_menusave,
        gensym("menusave"), 0);
    class_addmethod(c, (t_method)canvas_menusaveas,
        gensym("menusaveas"), 0);
947 948
    class_addmethod(c, (t_method)canvas_menuprint,
        gensym("menuprint"), 0);
949
}