/* * COPYRIGHT * * cschem - modular/flexible schematics editor - libcschem (core library) * Copyright (C) 2018,2019,2022 Tibor 'Igor2' Palinkas * * (Supported by NLnet NGI0 PET Fund in 2022) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version.* * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 31 Milk Street, # 960789 Boston, MA 02196 USA * * Contact: * Project page: http://repo.hu/projects/sch-rnd * contact lead developer: http://www.repo.hu/projects/sch-rnd/contact.html * mailing list: http://www.repo.hu/projects/sch-rnd/contact.html */ #include "config.h" #include "libcschem/common_types.h" #include #include #include #include #include #include #include "attrib.h" #include "cnc_obj.h" #include "cnc_grp.h" #include "undo.h" #include "concrete.h" #include "abstract.h" #include "project.h" #include "extobj.h" #include #include #include #include /*** low level attribute manipulation */ static csch_attrib_t *attr_alloc(const char *key, int prio) { csch_attrib_t *a = calloc(sizeof(csch_attrib_t), 1); a->key = rnd_strdup(key); a->prio = prio; return a; } static void attr_free_vts(vts0_t *vt) { size_t n; for(n = 0; n < vts0_len(vt); n++) free(vt->array[n]); vts0_uninit(vt); } static void attr_free_val(csch_attrib_t *a) { if (a->val != NULL) { free(a->val); a->val = NULL; } else attr_free_vts(&a->arr); } static void attr_free(csch_attrib_t *a) { free(a->key); attr_free_val(a); attr_free_vts(&a->source); free(a->export_name); free(a); } void csch_attr_free(csch_attrib_t *a) { attr_free(a); } #include "attrib_src.c" /*** API functions ***/ void csch_attrib_init(csch_attribs_t *attribs) { htsp_init(attribs, strhash, strkeyeq); } void csch_attrib_uninit(csch_attribs_t *attribs) { htsp_entry_t *e; for(e = htsp_first(attribs); e; e = htsp_next(attribs, e)) attr_free(e->value); htsp_uninit(attribs); } static int attrib_sort_cmp(const void *a1_, const void *a2_) { csch_attrib_t * const *a1 = a1_; csch_attrib_t * const *a2 = a2_; return strcmp((*a1)->key, (*a2)->key); } void csch_attrib_sort(vtp0_t *dst, const csch_attribs_t *attribs) { htsp_entry_t *e; /* optimization: make room for all the elements, avoiding realloc()s down the road */ vtp0_enlarge(dst, vtp0_len(dst) + attribs->used - 1); dst->used = 0; for(e = htsp_first(attribs); e; e = htsp_next(attribs, e)) vtp0_append(dst, e->value); qsort(dst->array, vtp0_len(dst), sizeof(csch_attrib_t *), attrib_sort_cmp); } /* Does not free src. Strdups val only if requested */ static int csch_attrib_set_nofree(csch_attribs_t *attribs, int prio, const char *key, const char *val, csch_source_arg_t *source, csch_attrib_t **attr_out, int strdup_val) { csch_attrib_t *a = htsp_get(attribs, key); if (a == NULL) { a = attr_alloc(key, prio); if (attr_out != NULL) *attr_out = a; htsp_set(attribs, a->key, a); } else { if (prio > a->prio) { append_src(a, prio, source, 1); if (attr_out != NULL) *attr_out = a; return -1; } attr_free_val(a); } if (strdup_val) a->val = ((val == NULL) ? NULL : rnd_strdup(val)); else a->val = (char *)val; /* caller explicitly asked for this cast */ append_src(a, prio, source, 0); if (attr_out != NULL) *attr_out = a; return 0; } int csch_attrib_set(csch_attribs_t *attribs, int prio, const char *key, const char *val, csch_source_arg_t *source, csch_attrib_t **attr_out) { int res = csch_attrib_set_nofree(attribs, prio, key, val, source, attr_out, 1); csch_attr_src_free(source); return res; } int csch_attrib_setptr(csch_attribs_t *attribs, int prio, char *key, const char *val, csch_source_arg_t *source, csch_attrib_t **attr_out) { int res = csch_attrib_set_nofree(attribs, prio, key, val, source, attr_out, 0); csch_attr_src_free(source); return res; } int csch_attrib_seti(csch_attribs_t *attribs, int prio, const char *key, long idx, const char *val, csch_source_arg_t *source, csch_attrib_t **attr_out) { csch_attrib_t *a = htsp_get(attribs, key); char *nval; if (idx < 0) goto error; if (a == NULL) { a = attr_alloc(key, prio); if (attr_out != NULL) *attr_out = a; htsp_set(attribs, a->key, a); } else { if (a->val != NULL) goto error; if (prio > a->prio) { append_src(a, prio, source, 1); if (attr_out != NULL) *attr_out = a; goto error; } } append_src(a, prio, source, 0); nval = rnd_strdup(val); if (idx < a->arr.used) { free(a->arr.array[idx]); a->arr.array[idx] = nval; } vts0_set(&a->arr, idx, nval); if (attr_out != NULL) *attr_out = a; csch_attr_src_free(source); return 0; error:; csch_attr_src_free(source); return -1; } /* how is: 0=overwrite whole arr; -1=prepend; +1=append; does not free src */ static int csch_attrib_set_arr_nofree(csch_attribs_t *attribs, int prio, const char *key, const vts0_t *val, csch_source_arg_t *source, csch_attrib_t **attr_out, int how) { long n; csch_attrib_t *a = htsp_get(attribs, key); if (a == NULL) { a = attr_alloc(key, prio); if (attr_out != NULL) *attr_out = a; htsp_set(attribs, a->key, a); } else { if (a->val != NULL) return -1; if (prio > a->prio) { append_src(a, prio, source, 1); if (attr_out != NULL) *attr_out = a; return -1; } if (how == 0) attr_free_val(a); } append_src(a, prio, source, 0); if (how == 0) { /* overwrite */ for(n = 0; n < a->arr.used; n++) free(a->arr.array[n]); a->arr.used = 0; for(n = 0; n < val->used; n++) vts0_set(&a->arr, n, rnd_strdup(val->array[n])); } else if (how > 1) { /* append */ for(n = 0; n < val->used; n++) vts0_append(&a->arr, rnd_strdup(val->array[n])); } else { /* prepend */ for(n = 0; n < val->used; n++) vts0_append(&a->arr, rnd_strdup(val->array[n])); } if (attr_out != NULL) *attr_out = a; return 0; } int csch_attrib_set_arr(csch_attribs_t *attribs, int prio, const char *key, const vts0_t *val, csch_source_arg_t *source, csch_attrib_t **attr_out) { int res = csch_attrib_set_arr_nofree(attribs, prio, key, val, source, attr_out, 0); csch_attr_src_free(source); return res; } int csch_attrib_append(csch_attribs_t *attribs, int prio, const char *key, const char *val, csch_source_arg_t *source) { int res; vts0_t tmp = {0}; tmp.used = tmp.alloced = 1; tmp.array = (char **)&val; res = csch_attrib_set_arr_nofree(attribs, prio, key, &tmp, source, NULL, 1); csch_attr_src_free(source); return res; } int csch_attrib_prepend(csch_attribs_t *attribs, int prio, const char *key, const char *val, csch_source_arg_t *source) { int res; vts0_t tmp = {0}; tmp.used = tmp.alloced = 1; tmp.array = (char **)&val; res = csch_attrib_set_arr_nofree(attribs, prio, key, &tmp, source, NULL, -1); csch_attr_src_free(source); return res; } int csch_attrib_append_val(csch_attribs_t *attribs, int prio, const char *key, const csch_attrib_t *srca, csch_source_arg_t *source) { if (srca->val != NULL) return csch_attrib_append(attribs, prio, key, srca->val, source); return csch_attrib_set_arr_nofree(attribs, prio, key, &srca->arr, source, NULL, 1); } int csch_attrib_set_arr_c(csch_attribs_t *attribs, int prio, const char *key, const char **val, csch_source_arg_t *source, csch_attrib_t **attr_out) { int res; const char **s; vts0_t tmp = {0}; for(s = val; *s != NULL; s++) tmp.used++; tmp.alloced = tmp.used; tmp.array = (char **)val; res = csch_attrib_set_arr_nofree(attribs, prio, key, &tmp, source, attr_out, 0); csch_attr_src_free(source); return res; } void csch_attrib_set_export_name(csch_attrib_t *dst, const char *expname) { free(dst->export_name); dst->export_name = rnd_strdup(expname); } csch_attrib_t *csch_attrib_dup_rename(csch_attrib_t *src, const char *new_key) { size_t n; csch_attrib_t *dst = calloc(sizeof(csch_attrib_t), 1); dst->key = rnd_strdup(new_key); if (src->val != NULL) dst->val = rnd_strdup(src->val); else dst->val = NULL; dst->prio = src->prio; if (src->arr.used > 0) { vts0_enlarge(&dst->arr, src->arr.used); dst->arr.used = 0; for(n = 0; n < src->arr.used; n++) vts0_append(&dst->arr, rnd_strdup(src->arr.array[n])); } if (src->source.used > 0) { vts0_enlarge(&dst->source, src->source.used); dst->source.used = 0; for(n = 0; n < src->source.used; n++) vts0_append(&dst->source, rnd_strdup_null(src->source.array[n])); } return dst; } csch_attrib_t *csch_attrib_dup(csch_attrib_t *src) { return csch_attrib_dup_rename(src, src->key); } int csch_attrib_del(csch_attribs_t *attribs, int prio, const char *key, csch_source_arg_t *source) { csch_attrib_t *a = htsp_get((csch_attribs_t *)attribs, key); if (a == NULL) { csch_attr_src_free(source); return -1; } a->deleted = 1; append_src(a, prio, source, 0); csch_attr_src_free(source); return 0; } void csch_attrib_copy_all(csch_attribs_t *dst, const csch_attribs_t *src) { htsp_entry_t *e; for(e = htsp_first(src); e; e = htsp_next(src, e)) { csch_attrib_t *a = csch_attrib_dup(e->value); htsp_set(dst, a->key, a); } } static csch_source_arg_t *attr_copy_gen_source(csch_attrib_t *s) { char *sep, *ssrc = s->source.array[0]; long len; csch_source_arg_t *src; sep = strchr(ssrc, ':'); sep++; len = strlen(sep); src = malloc(sizeof(csch_source_arg_t) + len + 1); src->type[0] = 'c'; src->type[1] = '\0'; memcpy(src->src_desc, sep, len+1); return src; } void csch_attrib_copy_all_undoable(csch_sheet_t *sheet, csch_cgrp_t *dst_obj, csch_attribs_t *dst, const csch_attribs_t *src, int undoable) { htsp_entry_t *e; assert((dst != NULL) || (dst_obj != NULL)); if (undoable) { assert(dst_obj != NULL); assert(sheet != NULL); } if (dst == NULL) dst = &dst_obj->attr; for(e = htsp_first(src); e; e = htsp_next(src, e)) { csch_attrib_t *s = csch_attrib_dup(e->value); csch_attrib_t *d = csch_attrib_get(dst, e->key); if ((s != NULL) && (d != NULL) && (csch_attrib_val_eq_(s, d))) { /* do not merge if values are the same */ attr_free(s); continue; } if (s->val == NULL) { /* source is an array */ int done = 0; if (s->arr.used > 0) { if ((d == NULL) || ((d->arr.used == 0) && (d->val == NULL))) { /* source has an array, destination has nothing: simply copy the array */ long n; if (undoable) { if (d == NULL) { d = attr_alloc(e->key, s->prio); htsp_set(dst, e->key, d); } vts0_append(&d->arr, NULL); /* dummy cursor for insert before */ for(n = 0; n < s->arr.used; n++) csch_attr_arr_modify_ins_before(sheet, dst_obj, e->key, n, s->arr.array[n], 1); d->arr.used--; /* remove dummy cursor */ done = 1; } else { csch_source_arg_t *source = attr_copy_gen_source(s); csch_attrib_set_arr(dst, s->prio, e->key, &s->arr, source, NULL); done = 1; } } } TODO("figure how to merge arrays with differing content"); if (!done) rnd_message(RND_MSG_ERROR, "csch_attrib_copy_all_undoable(): internal error: can't copy/merge array type attribte '%s'\n", e->key); } else { /* source is scalar */ TODO("handle if dst is an array"); if ((d == NULL) || ((d->val != NULL))) { csch_source_arg_t *source = attr_copy_gen_source(s); if (undoable) csch_attr_modify_str(sheet, dst_obj, s->prio, e->key, s->val, source, 1); else csch_attrib_set(dst, s->prio, e->key, s->val, source, NULL); attr_free(s); s = NULL; } } } } int csch_attrib_apply(csch_attribs_t *dst, const csch_attribs_t *src, csch_source_arg_t *source, int *force_prio) { htsp_entry_t *e; int err = 0; for(e = htsp_first(src); e; e = htsp_next(src, e)) { csch_attrib_t *a = e->value; int prio = (force_prio != NULL) ? *force_prio : a->prio; if (a->val != NULL) err |= csch_attrib_set_nofree(dst, prio, a->key, a->val, source, NULL, 1); else err |= csch_attrib_set_arr_nofree(dst, prio, a->key, &a->arr, source, NULL, 0); } csch_attr_src_free(source); return err; } /* invalidate all dyntext on attr change to make sure the new value is visible */ static void attr_side_dyntext(csch_cgrp_t *grp) { htip_entry_t *e; for(e = htip_first(&grp->id2obj); e != NULL; e = htip_next(&grp->id2obj, e)) { csch_chdr_t *o = e->value; if (csch_obj_is_grp(o)) attr_side_dyntext((csch_cgrp_t *)o); else if (o->type == CSCH_CTYPE_TEXT) { csch_text_t *t = (csch_text_t *)o; if (t->dyntext) { csch_text_dyntext_inval(t); csch_text_invalidate_font(t); } } } } void csch_attr_side_effects(csch_cgrp_t *grp, const char *key) { if (!csch_obj_is_grp(&grp->hdr)) return; attr_side_dyntext(grp); if (key != NULL) { const char *val = csch_attrib_get_str(&grp->attr, key); csch_cgrp_attrib_update(grp->hdr.sheet, grp, 0, key, val); } } RND_INLINE void notify(csch_chdr_t *obj) { rnd_event(&obj->sheet->hidlib, CSCH_EVENT_OBJ_ATTR_EDITED, "p", obj); csch_cobj_redraw(obj); } unsigned csch_attrib_hash(const csch_attribs_t *attr) { unsigned res = 0; htsp_entry_t *e; for(e = htsp_first(attr); e != NULL; e = htsp_next(attr, e)) { long n; csch_attrib_t *a = e->value; res ^= strhash(a->key); res ^= ((unsigned)a->prio) << 3; res ^= ((unsigned)a->deleted) << 15; if (a->val != NULL) res ^= strhash(a->val); res ^= ((unsigned)a->arr.used) << 12; for(n = 0; n < a->arr.used; n++) if (a->arr.array[n] != NULL) res ^= strhash(a->arr.array[n]); } return res; } int csch_attrib_val_eq_(const csch_attrib_t *a1, const csch_attrib_t *a2) { long n; if (a1->val != NULL) { if (a2->val == NULL) return 0; if (strcmp(a1->val, a2->val) != 0) return 0; } else if (a2->val != NULL) return 0; if (a1->arr.used != a2->arr.used) return 0; for(n = 0; n < a1->arr.used; n++) { if (a1->arr.array[n] != NULL) { if (a2->arr.array[n] == NULL) return 0; if (strcmp(a1->arr.array[n], a2->arr.array[n]) != 0) return 0; } else if (a2->arr.array[n] != NULL) return 0; } return 1; } int csch_attrib_eq_(const csch_attrib_t *a1, const csch_attrib_t *a2) { /* assume keys match */ if (a1->prio != a2->prio) return 0; if (a1->deleted != a2->deleted) return 0; return csch_attrib_val_eq_(a1, a2); } int csch_attrib_eq(const csch_attribs_t *attr1, const csch_attribs_t *attr2) { htsp_entry_t *e; if (htsp_length(attr1) != htsp_length(attr2)) return 0; for(e = htsp_first((htsp_t *)attr1); e != NULL; e = htsp_next((htsp_t *)attr1, e)) { csch_attrib_t *a1 = e->value, *a2 = htsp_get((htsp_t *)attr2, e->key); if (a2 == NULL) return 0; if (!csch_attrib_eq_(a1, a2)) return 0; } return 1; } /*** Modify ***/ typedef struct { csch_cgrp_t *obj; /* it is safe to save the object pointer because it is persistent (through the removed object list) */ csch_attrib_t *a; char *key; /* need to store a copy of key separately because on deletion key is free'd */ } undo_attr_modify_t; static int undo_attr_modify_swap(void *udata) { undo_attr_modify_t *u = udata; csch_attrib_t *a; csch_cobj_redraw(u->obj); csch_extrobj_attr_edit_pre(u->obj, u->key); a = htsp_get(&u->obj->attr, u->key); if (u->a != NULL) htsp_set(&u->obj->attr, u->a->key, u->a); else htsp_popentry(&u->obj->attr, u->key); u->a = a; csch_attr_side_effects(u->obj, u->key); csch_sheet_set_changed(u->obj->hdr.sheet, 1); csch_extrobj_attr_edit_post(u->obj, u->key); notify(&u->obj->hdr); return 0; } static void undo_attr_modify_print(void *udata, char *dst, size_t dst_len) { undo_attr_modify_t *u = udata; rnd_snprintf(dst, dst_len, "attr change, key=%s", u->key); } static void undo_attr_modify_free(void *udata) { undo_attr_modify_t *u = udata; if (u->a != NULL) { attr_free(u->a); u->a = NULL; } free(u->key); u->key = NULL; } static const char core_attr_cookie[] = "libcschem/core/attrib.c"; static const uundo_oper_t undo_attr_modify = { core_attr_cookie, undo_attr_modify_free, undo_attr_modify_swap, undo_attr_modify_swap, undo_attr_modify_print }; void csch_attr_modify_str(csch_sheet_t *sheet, csch_cgrp_t *obj, int prio, const char *key, const char *val, csch_source_arg_t *source, int undoable) { undo_attr_modify_t utmp, *u = &utmp; if (undoable) u = uundo_append(&sheet->undo, &undo_attr_modify, sizeof(undo_attr_modify_t)); if (prio < 0) { csch_attrib_t *a = htsp_get(&obj->attr, key); if (a != NULL) prio = a->prio; else prio = -prio; } u->obj = obj; u->a = attr_alloc(key, prio); u->a->val = val == NULL ? NULL : rnd_strdup(val); u->key = rnd_strdup(key); append_src(u->a, prio, source, 0); undo_attr_modify_swap(u); if (undoable) csch_undo_inc_serial(sheet); csch_attr_src_free(source); } void csch_attr_modify_del(csch_sheet_t *sheet, csch_cgrp_t *obj, const char *key, int undoable) { undo_attr_modify_t utmp, *u = &utmp; csch_attrib_t *a; /* skip creating an undo slot for a non-existing attribute's del (no-op) */ a = htsp_get(&obj->attr, key); if (a == NULL) return; if (undoable) u = uundo_append(&sheet->undo, &undo_attr_modify, sizeof(undo_attr_modify_t)); u->obj = obj; u->a = NULL; u->key = rnd_strdup(key); undo_attr_modify_swap(u); if (undoable) csch_undo_inc_serial(sheet); } void csch_attr_modify_rename(csch_sheet_t *sheet, csch_cgrp_t *grp, csch_attrib_t *a, const char *new_name, csch_source_arg_t *src, int undoable) { int prio = a->prio; assert(strcmp(new_name, a->key) != 0); if (undoable) uundo_freeze_serial(&sheet->undo); csch_attr_modify_str(sheet, grp, prio, new_name, a->val, src, undoable); csch_attr_modify_del(sheet, grp, a->key, undoable); if (undoable) { uundo_unfreeze_serial(&sheet->undo); uundo_inc_serial(&sheet->undo); } } typedef struct { csch_chdr_t *obj; csch_attrib_t *a; short int prio; } undo_attr_modify_prio_t; static int undo_attr_modify_prio_swap(void *udata) { undo_attr_modify_prio_t *u = udata; short int tmp; tmp = u->a->prio; u->a->prio = u->prio; u->prio = tmp; csch_sheet_set_changed(u->obj->sheet, 1); notify(u->obj); return 0; } static void undo_attr_modify_prio_print(void *udata, char *dst, size_t dst_len) { undo_attr_modify_prio_t *u = udata; rnd_snprintf(dst, dst_len, "attr prio change, key=%s %d %d", u->a->key, u->a->prio, u->prio); } static const uundo_oper_t undo_attr_modify_prio = { core_attr_cookie, /*undo_attr_modify_free*/ NULL, undo_attr_modify_prio_swap, undo_attr_modify_prio_swap, undo_attr_modify_prio_print }; void csch_attr_modify_prio(csch_sheet_t *sheet, csch_cgrp_t *grp, csch_attrib_t *a, int prio, csch_source_arg_t *source, int undoable) { undo_attr_modify_prio_t utmp, *u = &utmp; if (undoable) u = uundo_append(&sheet->undo, &undo_attr_modify_prio, sizeof(undo_attr_modify_prio_t)); u->obj = &grp->hdr; u->a = a; u->prio = prio; append_src(u->a, prio, source, 0); undo_attr_modify_prio_swap(u); if (undoable) csch_undo_inc_serial(sheet); csch_attr_src_free(source); } static const char *NEWVAL_DEL = "NEWVAL_DEL"; typedef struct { csch_cgrp_t *obj; /* it is safe to save the object pointer because it is persistent (through the removed object list) */ char *key; long idx; /* current index */ long delta; /* if non-zero, move item within the list */ char *newval; /* if non-NULL, replace item content; if NEWVAL_DEL, remove item */ char *saved; /* saved string value if newval is NEWVAL_DEL */ } undo_attr_arr_modify_t; /* returns delta achieved */ RND_INLINE long attr_arr_move(csch_attrib_t *a, long idx, long delta) { long n, end, res = 0; char *save; if (delta == 0) return 0; if (delta > 0) { if (idx >= a->arr.used-1) return 0; /* moving last item in positive dir -> noop */ end = idx + delta; if (end > a->arr.used-1) end = a->arr.used-1; save = a->arr.array[idx]; for(n = idx; n < end; n++,res++) a->arr.array[n] = a->arr.array[n+1]; a->arr.array[end] = save; } else { /* delta < 0 */ if (idx <= 0) return 0; /* moving first item in negative dir -> noop */ end = idx + delta; if (end < 0) end = 0; save = a->arr.array[idx]; for(n = idx; n > end; n--,res++) a->arr.array[n] = a->arr.array[n-1]; a->arr.array[end] = save; } return res; } RND_INLINE void attr_arr_del(csch_attrib_t *a, long idx) { long n, end = a->arr.used-1; for(n = idx; n < end; n++) a->arr.array[n] = a->arr.array[n+1]; a->arr.used--; } /* create a new, empty slot at [idx], moving all items starting from idx to +1. Effectively inserts an empty slot at idx, before the item that was at idx. */ RND_INLINE void attr_arr_ins_before(csch_attrib_t *a, long idx) { long n, end = a->arr.used; vts0_enlarge(&a->arr, a->arr.used); for(n = end; n > idx; n--) a->arr.array[n] = a->arr.array[n-1]; a->arr.array[idx] = NULL; } static int undo_attr_arr_modify_swap(void *udata) { undo_attr_arr_modify_t *u = udata; csch_attrib_t *a; long end; a = htsp_get(&u->obj->attr, u->key); if (a == NULL) { rnd_message(RND_MSG_ERROR, "undo_attr_arr_modify_swap(): no such attribute '%s' in object\n", u->key); return -1; } end = a->arr.used; if ((u->newval == NEWVAL_DEL) && (u->saved != NULL)) end++; /* special case: allow (re-)insert after the last item */ if ((u->idx < 0) || (u->idx >= end)) { rnd_message(RND_MSG_ERROR, "undo_attr_arr_modify_swap(): arr index %ld out of range (0..%ld)\n", u->idx, a->arr.used); return -1; } csch_cobj_redraw(u->obj); if (u->delta != 0) u->delta = -attr_arr_move(a, u->idx, u->delta); if (u->newval == NEWVAL_DEL) { if (u->saved == NULL) { u->saved = a->arr.array[u->idx]; attr_arr_del(a, u->idx); } else { attr_arr_ins_before(a, u->idx); a->arr.array[u->idx] = u->saved; u->saved = NULL; } } else if (u->newval != NULL) rnd_swap(char *, a->arr.array[u->idx], u->newval); csch_attr_side_effects(u->obj, u->key); csch_sheet_set_changed(u->obj->hdr.sheet, 1); notify(&u->obj->hdr); return 0; } static void undo_attr_arr_modify_print(void *udata, char *dst, size_t dst_len) { undo_attr_arr_modify_t *u = udata; if (u->newval == NEWVAL_DEL) rnd_snprintf(dst, dst_len, "attr arr %s, key=%s:%ld", (u->saved == NULL ? "del" : "ins"), u->key, u->idx); else if (u->newval != NULL) rnd_snprintf(dst, dst_len, "attr arr change, key=%s:%ld '%s'", u->key, u->idx, u->newval); else if (u->delta != 0) rnd_snprintf(dst, dst_len, "attr arr move, key=%s:%ld by %ld", u->key, u->idx, u->delta); else rnd_snprintf(dst, dst_len, "attr arr noop, key=%s:%ld", u->key, u->idx); } static void undo_attr_arr_modify_free(void *udata) { undo_attr_arr_modify_t *u = udata; free(u->key); u->key = NULL; if (u->newval != NEWVAL_DEL) free(u->newval); u->newval = NULL; free(u->saved); u->saved = NULL; } static const uundo_oper_t undo_attr_arr_modify = { core_attr_cookie, undo_attr_arr_modify_free, undo_attr_arr_modify_swap, undo_attr_arr_modify_swap, undo_attr_arr_modify_print }; void csch_attr_arr_modify_str(csch_sheet_t *sheet, csch_cgrp_t *obj, const char *key, long idx, const char *newval, int undoable) { undo_attr_arr_modify_t utmp, *u = &utmp; if (undoable) u = uundo_append(&sheet->undo, &undo_attr_arr_modify, sizeof(undo_attr_arr_modify_t)); u->obj = obj; u->key = rnd_strdup(key); u->idx = idx; u->delta = 0; u->newval = (newval == NEWVAL_DEL ? (char *)NEWVAL_DEL : rnd_strdup(newval)); u->saved = NULL; undo_attr_arr_modify_swap(u); if (undoable) csch_undo_inc_serial(sheet); } void csch_attr_arr_modify_del(csch_sheet_t *sheet, csch_cgrp_t *obj, const char *key, long idx, int undoable) { csch_attr_arr_modify_str(sheet, obj, key, idx, NEWVAL_DEL, undoable); } void csch_attr_arr_modify_move(csch_sheet_t *sheet, csch_cgrp_t *obj, const char *key, long idx, long delta, int undoable) { undo_attr_arr_modify_t utmp, *u = &utmp; csch_attrib_t *a = htsp_get(&obj->attr, key); /* don't do anything on edges to save undo slots */ if (a == NULL) return; if ((idx < 0) || (idx >= a->arr.used)) return; if (delta == 0) return; if ((idx == 0) && (delta < 0)) return; if ((idx == a->arr.used-1) && (delta > 0)) return; if (undoable) u = uundo_append(&sheet->undo, &undo_attr_arr_modify, sizeof(undo_attr_arr_modify_t)); u->obj = obj; u->key = rnd_strdup(key); u->idx = idx; u->delta = delta; u->newval = NULL; u->saved = NULL; undo_attr_arr_modify_swap(u); if (undoable) csch_undo_inc_serial(sheet); } void csch_attr_arr_modify_ins_before(csch_sheet_t *sheet, csch_cgrp_t *obj, const char *key, long idx, const char *newval, int undoable) { undo_attr_arr_modify_t utmp, *u = &utmp; if (undoable) u = uundo_append(&sheet->undo, &undo_attr_arr_modify, sizeof(undo_attr_arr_modify_t)); u->obj = obj; u->key = rnd_strdup(key); u->idx = idx; u->delta = 0; u->newval = (char *)NEWVAL_DEL; u->saved = rnd_strdup(newval); undo_attr_arr_modify_swap(u); if (undoable) csch_undo_inc_serial(sheet); } typedef struct { csch_cgrp_t *obj; /* it is safe to save the object pointer because it is persistent (through the removed object list) */ char *key; /* swap values */ char *val; /* scalar; if NULL, use arr */ vts0_t arr; } undo_attr_modify_conv_t; static int undo_attr_modify_conv_swap(void *udata) { undo_attr_modify_conv_t *u = udata; csch_attrib_t *a; a = htsp_get(&u->obj->attr, u->key); if (a == NULL) { rnd_message(RND_MSG_ERROR, "Internal error: undo_attr_modify_conv_swap(): attrib not found\n"); return -1; } if (u->val == NULL) { if (a->val == NULL) { rnd_message(RND_MSG_ERROR, "Internal error: undo_attr_modify_conv_swap(): converting array to array?\n"); return -1; } u->val = a->val; a->val = NULL; a->arr = u->arr; memset(&u->arr, 0, sizeof(u->arr)); } else { /* u->val != NULL */ if (a->val != NULL) { rnd_message(RND_MSG_ERROR, "Internal error: undo_attr_modify_conv_swap(): converting string to string?\n"); return -1; } a->val = u->val; u->val = NULL; u->arr = a->arr; memset(&a->arr, 0, sizeof(a->arr)); } csch_cobj_redraw(u->obj); csch_attr_side_effects(u->obj, u->key); csch_sheet_set_changed(u->obj->hdr.sheet, 1); notify(&u->obj->hdr); return 0; } static void undo_attr_modify_conv_print(void *udata, char *dst, size_t dst_len) { undo_attr_modify_conv_t *u = udata; rnd_snprintf(dst, dst_len, "attr conversion between string and array key=%s", u->key); } static void undo_attr_modify_conv_free(void *udata) { undo_attr_modify_conv_t *u = udata; free(u->val); u->val = NULL; attr_free_vts(&u->arr); free(u->key); u->key = NULL; } static const uundo_oper_t undo_attr_modify_conv = { core_attr_cookie, undo_attr_modify_conv_free, undo_attr_modify_conv_swap, undo_attr_modify_conv_swap, undo_attr_modify_conv_print }; void csch_attr_modify_conv_to_arr(csch_sheet_t *sheet, csch_cgrp_t *obj, const char *key, int undoable) { undo_attr_modify_conv_t utmp, *u = &utmp; csch_attrib_t *a = htsp_get(&obj->attr, key); if ((a == NULL) || (a->val == NULL)) return; /* doesn't exist or already a string */ if (undoable) u = uundo_append(&sheet->undo, &undo_attr_modify_conv, sizeof(undo_attr_modify_conv_t)); memset(u, 0, sizeof(undo_attr_modify_conv_t)); u->obj = obj; u->key = rnd_strdup(key); if (*a->val != '\0') vts0_append(&u->arr, rnd_strdup(a->val)); undo_attr_modify_conv_swap(u); if (undoable) csch_undo_inc_serial(sheet); } void csch_attr_modify_conv_to_str(csch_sheet_t *sheet, csch_cgrp_t *obj, const char *key, int undoable) { undo_attr_modify_conv_t utmp, *u = &utmp; csch_attrib_t *a = htsp_get(&obj->attr, key); long n; gds_t tmp = {0}; if ((a == NULL) || (a->val != NULL)) return; /* doesn't exist or already a string */ if (undoable) u = uundo_append(&sheet->undo, &undo_attr_modify_conv, sizeof(undo_attr_modify_conv_t)); memset(u, 0, sizeof(undo_attr_modify_conv_t)); u->obj = obj; u->key = rnd_strdup(key); /* concat array members with ';' as separator */ for(n = 0; n < a->arr.used; n++) { if (n > 0) gds_append(&tmp, ';'); gds_append_str(&tmp, a->arr.array[n]); } if (tmp.array == NULL) u->val = rnd_strdup(""); else u->val = tmp.array; undo_attr_modify_conv_swap(u); if (undoable) csch_undo_inc_serial(sheet); }