g_readwrite.c 33.6 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
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);

405 406
void binbuf_savetext(t_binbuf *bfrom, t_binbuf *bto);

Miller Puckette's avatar
Miller Puckette committed
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 446 447
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;
448
            t_symbol *arraytemplatesym = template->t_vec[i].ds_fieldtemplate;
Miller Puckette's avatar
Miller Puckette committed
449 450 451 452 453 454 455
            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)
        {
456 457 458
            /* 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
459 460
            binbuf_addsemi(b);
        }
461 462 463 464 465
        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
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 500 501
    }
}

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;
502
            t_symbol *arraytemplatesym = ds->ds_fieldtemplate;
Miller Puckette's avatar
Miller Puckette committed
503 504 505 506 507 508 509
            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)
510
        {
511 512 513 514
            /* 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);
515
        }
516 517
        else if (ds->ds_type == DT_TEXT)
        {
Jonathan Wilkes's avatar
Jonathan Wilkes committed
518 519
            //canvas_addtemplatesforlist(w->w_list->gl_list,
            //    p_ntemplates, p_templatevec);
520
        }
Miller Puckette's avatar
Miller Puckette committed
521 522 523
    }
}

524 525 526 527 528 529 530 531 532 533 534 535
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)
        {
536
            t_symbol *arraytemplatesym = ds->ds_fieldtemplate;
537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553
            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
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 599 600
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;
601 602
                case DT_LIST: type = gensym("canvas"); break;
                case DT_TEXT: type = &s_list; break;
Miller Puckette's avatar
Miller Puckette committed
603 604
                default: type = &s_float; bug("canvas_write");
            }
605 606
            if (template->t_vec[j].ds_type == DT_ARRAY ||
                template->t_vec[j].ds_type == DT_LIST)
Miller Puckette's avatar
Miller Puckette committed
607
                binbuf_addv(b, "sss;", type, template->t_vec[j].ds_name,
608
                    gensym(template->t_vec[j].ds_fieldtemplate->s_name + 3));
Miller Puckette's avatar
Miller Puckette committed
609 610 611 612 613 614 615 616 617 618 619 620 621 622 623
            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);
        }
    }
624
    t_freebytes(templatevec, ntemplates * sizeof(*templatevec));
Miller Puckette's avatar
Miller Puckette committed
625 626 627 628 629
    return (b);
}

static void glist_write(t_glist *x, t_symbol *filename, t_symbol *format)
{
Mathieu L Bouchard's avatar
Mathieu L Bouchard committed
630
    int cr = 0;
Miller Puckette's avatar
Miller Puckette committed
631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650
    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. ----*/

651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672
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
673 674 675 676 677 678 679
    /* 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;
680
    extern int sys_zoom;
Miller Puckette's avatar
Miller Puckette committed
681 682 683 684
        /* subpatch */
    if (x->gl_owner && !x->gl_env)
    {
        /* have to go to original binbuf to find out how we were named. */
685
        //fprintf(stderr,"saving subpatch\n");
Miller Puckette's avatar
Miller Puckette committed
686
        t_binbuf *bz = binbuf_new();
687 688
        t_symbol *patchsym, *selector;
        int name_index;
Miller Puckette's avatar
Miller Puckette committed
689
        binbuf_addbinbuf(bz, x->gl_obj.ob_binbuf);
690 691 692 693 694 695 696 697
        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
698
        binbuf_free(bz);
699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723
        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
724 725
        /* Not sure what the following commented line was doing... */
        //t_text *xt = (t_text *)x;
Miller Puckette's avatar
Miller Puckette committed
726 727 728 729
    }
        /* root or abstraction */
    else 
    {
730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745
        // 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
746 747 748 749 750 751 752 753
        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))
    {
754 755 756 757 758 759 760
        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
761 762 763 764 765 766 767 768 769 770 771 772 773
    }
        /* 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,
774 775 776
                (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
777 778 779 780
                    /* 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,
781 782
                (t_float)x->gl_pixwidth, (t_float)x->gl_pixheight,
                (t_float)x->gl_isgraph);
Miller Puckette's avatar
Miller Puckette committed
783
    }
784 785 786 787 788 789 790
        /* 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
791 792
}

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

Miller Puckette's avatar
Miller Puckette committed
796 797 798 799 800 801 802 803 804 805 806 807 808
    /* 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);
809 810 811 812
        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
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 844 845
        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;
846 847
                case DT_LIST: type = gensym("canvas"); break;
                case DT_TEXT: type = gensym("text"); break; //&s_list; break;
Miller Puckette's avatar
Miller Puckette committed
848 849
                default: type = &s_float; bug("canvas_write");
            }
850 851 852 853
            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
854
                binbuf_addv(b, "sss", type, template->t_vec[j].ds_name,
855
                    gensym(template->t_vec[j].ds_fieldtemplate->s_name + 3));
Miller Puckette's avatar
Miller Puckette committed
856 857 858 859 860 861 862
            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);
863
extern void canvasgop_checksize(t_canvas *x);
Miller Puckette's avatar
Miller Puckette committed
864 865 866

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

894
static void canvas_menusaveas(t_canvas *x, t_floatarg fdestroy)
Miller Puckette's avatar
Miller Puckette committed
895 896
{
    t_canvas *x2 = canvas_getrootfor(x);
897 898 899 900 901 902
    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
903 904
}

905
static void canvas_menusave(t_canvas *x, t_floatarg fdestroy)
Miller Puckette's avatar
Miller Puckette committed
906 907 908 909
{
    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
910 911
            && (strlen(name) < 4 || strcmp(name + strlen(name)-4, ".pat")
                || strcmp(name + strlen(name)-4, ".mxt")))
912 913
            canvas_savetofile(x2, x2->gl_name, canvas_getdir(x2), fdestroy);
    else canvas_menusaveas(x2, fdestroy);
Miller Puckette's avatar
Miller Puckette committed
914 915
}

916 917 918 919 920 921
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
922 923 924 925 926 927 928 929 930
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,
931
        gensym("savetofile"), A_SYMBOL, A_SYMBOL, A_DEFFLOAT, 0);
Miller Puckette's avatar
Miller Puckette committed
932 933 934 935
    class_addmethod(canvas_class, (t_method)canvas_saveto,
        gensym("saveto"), A_CANT, 0);
/* ------------------ from the menu ------------------------- */
    class_addmethod(canvas_class, (t_method)canvas_menusave,
936
        gensym("menusave"), A_DEFFLOAT, 0);
Miller Puckette's avatar
Miller Puckette committed
937
    class_addmethod(canvas_class, (t_method)canvas_menusaveas,
938
        gensym("menusaveas"), A_DEFFLOAT, 0);
939 940
    class_addmethod(canvas_class, (t_method)canvas_menuprint,
        gensym("menuprint"), 0);
Miller Puckette's avatar
Miller Puckette committed
941
}
942 943 944 945 946 947 948

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);
949 950
    class_addmethod(c, (t_method)canvas_menuprint,
        gensym("menuprint"), 0);
951
}