/* * COPYRIGHT * * cschem - modular/flexible schematics editor - libcschem (core library) * Copyright (C) 2018,2022,2023 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 #include #include #include #include "event.h" #include "concrete.h" #include "cnc_obj.h" #include "cnc_text.h" #include "cnc_any_obj.h" #include "cnc_pen.h" #include "undo.h" #include "op_common.h" csch_cpen_t csch_pen_default_unknown = { {{0}, 0, CSCH_CTYPE_PEN, 0}, {""}, /* tip */ CSCH_PSHP_ROUND, 1, {0xF0, 0xC0, 0x00, 0x00, 0xF0C00000, 0.95, 0.8, 0, 0, "#F0C000"}, 0, 0, /* font */ 3500, NULL, NULL }; static const char *pen_shape_names[] = {"round", "square"}; const char *csch_pen_shape_name(csch_pen_shape_t shape) { if ((shape < 0) || (shape >= sizeof(pen_shape_names) / sizeof(pen_shape_names[0]))) return "invalid"; return pen_shape_names[shape]; } csch_cpen_t *csch_pen_alloc(csch_sheet_t *sheet, csch_cgrp_t *parent, const char *name) { csch_cpen_t *pen; csch_oid_t oid; sheet->auto_oid--; oid = sheet->auto_oid; pen = htip_get(&parent->id2obj, oid); if (pen != NULL) return NULL; pen = calloc(sizeof(csch_cpen_t), 1); csch_cobj_init(&pen->hdr, sheet, parent, oid, CSCH_CTYPE_PEN); pen->name = csch_comm_str(sheet, name, 1); if (pen->name.str != NULL) htsp_set(&parent->name2pen, (char *)pen->name.str, pen); return pen; } csch_cpen_t *csch_pen_dup2(csch_sheet_t *sheet, csch_cgrp_t *parent, const csch_cpen_t *src, const char *new_name) { csch_cpen_t *dst = csch_pen_alloc(sheet, parent, new_name); dst->shape = src->shape; dst->size = src->size; dst->color = src->color; dst->dash = src->dash; dst->font_height = src->font_height; dst->font_family = src->font_family == NULL ? NULL : rnd_strdup(src->font_family); dst->font_style = src->font_style == NULL ? NULL : rnd_strdup(src->font_style); return dst; } csch_cpen_t *csch_pen_dup(csch_sheet_t *sheet, csch_cgrp_t *parent, const csch_cpen_t *src) { return csch_pen_dup2(sheet, parent, src, src->name.str); } void (*csch_cb_pen_invalidate_font)(csch_sheet_t *sheet, csch_cpen_t *pen); RND_INLINE int csch_pen_same(csch_cpen_t *pen, csch_chdr_t *hdr, int is_fill) { csch_comm_str_t *target; if (is_fill) target = &hdr->stroke_name; else target = &hdr->fill_name; if (target->str == NULL) return 0; /* it is safe to compare str pointers because they are from the same hash */ return target->str == pen->name.str; } /* Remove cached ->pen pointers that depend on the given pen from all object headers that have it, recursively down from grp */ static void csch_pen_invalidate_(csch_cpen_t *pen, csch_cgrp_t *grp, int tip, int font) { htip_entry_t *e; for(e = htip_first(&grp->id2obj); e; e = htip_next(&grp->id2obj, e)) { csch_chdr_t *hdr = e->value; if ((hdr->stroke == pen) || csch_pen_same(pen, hdr, 0)) { if (tip) hdr->stroke = NULL; if (hdr->type == CSCH_CTYPE_TEXT) csch_text_invalidate_font((csch_text_t *)hdr); } if (tip && ((hdr->fill == pen) || csch_pen_same(pen, hdr, 1))) hdr->fill = NULL; if (csch_obj_is_grp(hdr)) csch_pen_invalidate_(pen, (csch_cgrp_t *)hdr, tip, font); } } void csch_pen_invalidate(csch_cpen_t *pen, int tip, int font, int remov) { csch_sheet_t *sheet = pen->hdr.sheet; htip_entry_t *e; if ((tip == 0) && (font == 0)) return; if (sheet != NULL) csch_pen_invalidate_(pen, pen->hdr.parent, tip, font); if (font && (csch_cb_pen_invalidate_font != NULL)) csch_cb_pen_invalidate_font(pen->hdr.sheet, pen); if (remov && (pen->name.str != NULL)) { htsp_popentry(&pen->hdr.parent->name2pen, pen->name.str); /* it's unlikely, but if another pen has the same name in the same group, we should fall back to that */ for(e = htip_first(&pen->hdr.parent->id2obj); e; e = htip_next(&pen->hdr.parent->id2obj, e)) { csch_cpen_t *p = e->value; if ((p->hdr.type == CSCH_CTYPE_PEN) && (p != pen) && (p->name.str != NULL) && (p->name.str == pen->name.str)) { htsp_set(&pen->hdr.parent->name2pen, (char *)p->name.str, p); break; } } } } void csch_pen_free(csch_cpen_t *pen) { /* remove cached pens from any object and pen from group */ csch_pen_invalidate(pen, 1, 1, 1); free(pen->font_family); free(pen->font_style); csch_cobj_uninit(&pen->hdr); free(pen); } csch_cpen_t *csch_pen_get(csch_sheet_t *sheet, csch_cgrp_t *grp, const char *name) { csch_cpen_t *pen; if (name == NULL) return NULL; pen = htsp_get(&grp->name2pen, name); if ((pen != NULL) && (pen->hdr.type != CSCH_CTYPE_PEN)) return NULL; return pen; } csch_cpen_t *csch_pen_resolve(csch_cgrp_t *grp, const char *pen_name) { while((grp != NULL) && csch_obj_is_grp(&grp->hdr)) { csch_cpen_t *pen = csch_pen_get(grp->hdr.sheet, grp, pen_name); if (pen != NULL) return pen; grp = grp->hdr.parent; } return NULL; } static const char *pen_new_name(csch_cgrp_t *parent, char *tmp, int len) { int n = 0; #define PREFIX "new_pen" if (htsp_get(&parent->name2pen, PREFIX) == NULL) return PREFIX; for(n = 2; n < 32766; n++) { rnd_snprintf(tmp, len, PREFIX "_%d", n); if (htsp_get(&parent->name2pen, tmp) == NULL) return tmp; } #undef PREFIX fprintf(stderr, "Failed to allocate new pen name - it's unlikely you have 2^15 new pens!\n"); abort(); } static csch_chdr_t *pen_create(csch_sheet_t *sheet, csch_cgrp_t *parent) { char tmp[128]; csch_cpen_t *pen = csch_pen_alloc(sheet, parent, pen_new_name(parent, tmp, sizeof(tmp))); if (pen == NULL) return NULL; return &pen->hdr; } static void pen_remove_alloc(csch_undo_remove_t *slot) { } static void pen_remove_redo(csch_undo_remove_t *slot) { csch_pen_invalidate((csch_cpen_t *)slot->obj, 1, 1, 1); csch_cnc_common_remove_redo(slot, CSCH_REM_FROM_RTREE | CSCH_REM_FROM_PARENT | CSCH_REM_DEL_EMPTY_PARENT); } static void pen_remove_undo(csch_undo_remove_t *slot) { csch_cpen_t *pen = (csch_cpen_t *)slot->obj; csch_cnc_common_remove_undo(slot, CSCH_REM_FROM_RTREE | CSCH_REM_FROM_PARENT | CSCH_REM_DEL_EMPTY_PARENT); if (pen->name.str != NULL) htsp_set(&pen->hdr.parent->name2pen, (char *)pen->name.str, pen); } static int pen_isc_with_box(csch_chdr_t *obj, csch_rtree_box_t *box) { /* pens do not have bounding box */ return 0; } const csch_ops_t csch_ops_pen = { pen_create, pen_remove_alloc, pen_remove_redo, pen_remove_undo, pen_isc_with_box }; /*** hash ***/ unsigned csch_pen_hash(const csch_cpen_t *pen, csch_hash_ignore_t ignore) { unsigned res = csch_chdr_hash(&pen->hdr); res ^= strhash(pen->name.str); res ^= ((unsigned)pen->shape); res ^= csch_coord_hash(pen->size); res ^= (unsigned)pen->color.r << 16 | (unsigned)pen->color.g << 8 | (unsigned)pen->color.b; res ^= ((unsigned)pen->dash) << 3; res ^= csch_coord_hash(pen->dash_period); return res; } int csch_pen_keyeq(const csch_cpen_t *p1, const csch_cpen_t *p2, csch_hash_ignore_t ignore) { if (!csch_chdr_eq(&p1->hdr, &p2->hdr)) return 0; if (p1->shape != p2->shape) return 0; if (p1->size != p2->size) return 0; if (p1->color.packed != p2->color.packed) return 0; if (p1->dash != p2->dash) return 0; if (p1->dash_period != p2->dash_period) return 0; if (p1->hdr.sheet == p2->hdr.sheet) { if (p1->name.str != p2->name.str) return 0; } else { if (strcmp(p1->name.str, p2->name.str) != 0) return 0; } return 1; } /*** Modify ***/ typedef struct { csch_cpen_t *pen; /* it is safe to save the object pointer because it is persistent (through the removed object list) */ csch_pen_shape_t shape; csch_coord_t size; rnd_color_t color; csch_comm_str_t name; unsigned short dash; csch_coord_t dash_period; unsigned copied_from_default:1; } undo_pen_modify_tip_t; static int undo_pen_modify_tip_swap(void *udata) { undo_pen_modify_tip_t *u = udata; csch_sheet_t *sheet = u->pen->hdr.sheet; if (u->name.str != u->pen->name.str) csch_pen_invalidate(u->pen, 1, 1, 1); else csch_pen_invalidate(u->pen, 0, 1, 0); /* font color may depend on re-render */ rnd_swap(csch_pen_shape_t, u->shape, u->pen->shape); rnd_swap(csch_coord_t, u->size, u->pen->size); rnd_swap(rnd_color_t, u->color, u->pen->color); rnd_swap(csch_comm_str_t, u->name, u->pen->name); rnd_swap(unsigned short, u->dash, u->pen->dash); rnd_swap(csch_coord_t, u->dash_period, u->pen->dash_period); rnd_swap(int, u->copied_from_default, u->pen->copied_from_default); if (u->name.str != u->pen->name.str) htsp_set(&u->pen->hdr.parent->name2pen, (char *)u->pen->name.str, u->pen); csch_pen_update_objs_for(sheet, &sheet->direct, u->pen); csch_sheet_set_changed(u->pen->hdr.sheet, 1); return 0; } static void undo_pen_modify_tip_print(void *udata, char *dst, size_t dst_len) { undo_pen_modify_tip_t *u = udata; if (u->pen->name.str != u->name.str) rnd_snprintf(dst, dst_len, "pen name change: %s <--> %s", u->pen->name.str, u->name.str); else rnd_snprintf(dst, dst_len, "pen tip modification (in %s)", u->pen->name.str); } static const char core_cpen_cookie[] = "libcschem/core/cnc_pen.c"; static const uundo_oper_t undo_pen_modify_tip = { core_cpen_cookie, NULL, undo_pen_modify_tip_swap, undo_pen_modify_tip_swap, undo_pen_modify_tip_print }; void csch_pen_modify_tip(csch_sheet_t *sheet, csch_cpen_t *pen, csch_pen_shape_t *shape, csch_coord_t *size, rnd_color_t *color, char **name, unsigned short *dash, csch_coord_t *dash_period, int undoable) { undo_pen_modify_tip_t utmp, *u = &utmp; if (undoable) u = uundo_append(&sheet->undo, &undo_pen_modify_tip, sizeof(undo_pen_modify_tip_t)); u->pen = pen; u->shape = CSCH_UNDO_MODIFY(pen, shape); u->size = CSCH_UNDO_MODIFY(pen, size); u->color = CSCH_UNDO_MODIFY(pen, color); u->dash = CSCH_UNDO_MODIFY(pen, dash); u->dash_period = CSCH_UNDO_MODIFY(pen, dash_period); u->copied_from_default = 0; /* always reset */ if (name == NULL) u->name = pen->name; else u->name = csch_comm_str(sheet, *name, 1); csch_pen_update_objs_for(sheet, &sheet->direct, u->pen); undo_pen_modify_tip_swap(u); if (undoable) csch_undo_inc_serial(sheet); } typedef struct { csch_cpen_t *pen; /* it is safe to save the object pointer because it is persistent (through the removed object list) */ csch_coord_t font_height; char *font_family; char *font_style; unsigned copied_from_default:1; } undo_pen_modify_font_t; static int undo_pen_modify_font_swap(void *udata) { undo_pen_modify_font_t *u = udata; csch_sheet_t *sheet = u->pen->hdr.sheet; csch_pen_invalidate(u->pen, 0, 1, 0); rnd_swap(csch_coord_t, u->font_height, u->pen->font_height); rnd_swap(char *, u->font_family, u->pen->font_family); rnd_swap(char *, u->font_style, u->pen->font_style); rnd_swap(int, u->copied_from_default, u->pen->copied_from_default); csch_pen_update_objs_for(sheet, &sheet->direct, u->pen); csch_sheet_set_changed(sheet, 1); return 0; } static void undo_pen_modify_font_print(void *udata, char *dst, size_t dst_len) { undo_pen_modify_font_t *u = udata; rnd_snprintf(dst, dst_len, "pen font modification (in %s)", u->pen->name.str); } static void undo_pen_modify_font_free(void *udata) { undo_pen_modify_font_t *u = udata; if (u->font_family != u->pen->font_family) free(u->font_family); if (u->font_style != u->pen->font_style) free(u->font_style); } static const uundo_oper_t undo_pen_modify_font = { core_cpen_cookie, undo_pen_modify_font_free, undo_pen_modify_font_swap, undo_pen_modify_font_swap, undo_pen_modify_font_print }; void csch_pen_modify_font(csch_sheet_t *sheet, csch_cpen_t *pen, csch_coord_t *font_height, char **font_family, char **font_style, int undoable) { undo_pen_modify_font_t utmp, *u = &utmp; if (undoable) u = uundo_append(&sheet->undo, &undo_pen_modify_font, sizeof(undo_pen_modify_font_t)); u->pen = pen; u->font_height = CSCH_UNDO_MODIFY(pen, font_height); u->font_family = CSCH_UNDO_MODIFY(pen, font_family); u->font_style = CSCH_UNDO_MODIFY(pen, font_style); u->copied_from_default = 0; /* always reset */ undo_pen_modify_font_swap(u); if (undoable) csch_undo_inc_serial(sheet); } void csch_pen_update_objs_for(csch_sheet_t *sheet, csch_cgrp_t *parent, csch_cpen_t *pen) { htip_entry_t *e; for(e = htip_first(&parent->id2obj); e != NULL; e = htip_next(&parent->id2obj, e)) { csch_chdr_t *obj = e->value; if (!csch_obj_is_grp(obj)) { if (obj->stroke == pen) { if (obj->type == CSCH_CTYPE_TEXT) { ((csch_text_t *)obj)->bbox_calced = 0; csch_cobj_update(sheet, obj, 0); } else csch_cobj_update(sheet, obj, 1); /* have to redo xform for the new pen */ } } else csch_pen_update_objs_for(sheet, (csch_cgrp_t *)obj, pen); } }