/* * COPYRIGHT * * cschem - modular/flexible schematics editor - libcschem (core library) * Copyright (C) 2020,2022,2024 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 "concrete.h" #include #include "event.h" #include "cnc_obj.h" #include "cnc_grp.h" #include "cnc_grp_child.h" #include "cnc_conn.h" #include "cnc_any_obj.h" #include "operation.h" #include "search.h" #include "op_common.h" #include "undo.h" #include "util_wirenet.h" extern const csch_ops_t csch_ops_line; extern const csch_ops_t csch_ops_arc; extern const csch_ops_t csch_ops_poly; extern const csch_ops_t csch_ops_text; extern const csch_ops_t csch_ops_conn; extern const csch_ops_t csch_ops_cgrp; extern const csch_ops_t csch_ops_cgrp_ref; extern const csch_ops_t csch_ops_pen; static const csch_ops_t *ops[CSCH_CTYPE_max] = { NULL, /* invalid */ &csch_ops_line, &csch_ops_arc, &csch_ops_poly, &csch_ops_text, NULL, /* CSCH_CTYPE_BITMAP */ &csch_ops_conn, &csch_ops_cgrp, &csch_ops_cgrp_ref, &csch_ops_pen }; const csch_ops_t *csch_op_get(csch_ctype_t type) { if ((type < 0) || (type >= CSCH_CTYPE_max)) return NULL; return ops[type]; } RND_INLINE csch_chdr_t *csch_cnc_remove_(csch_sheet_t *sheet, csch_chdr_t *obj, csch_cnc_common_remove_t how) { const csch_ops_t *ops = csch_op_get(obj->type); csch_undo_remove_t *u, utmp; csch_cgrp_t *parent = obj->parent; assert(ops != NULL); assert(ops->remove_redo != NULL); if (how & CSCH_REM_UNDOABLE) u = csch_op_remove_alloc_undo(sheet); else u = &utmp; u->obj = obj; u->sheet = sheet; uundo_freeze_serial(&sheet->undo); /* remove_alloc() may have side effects: undoable changes to other related objects; bundle them */ ops->remove_alloc(u); u->side_effects = 1; ops->remove_redo(u); u->side_effects = 0; if (how & CSCH_REM_UNDOABLE) csch_conn_auto_recalc(sheet, obj); if ((parent != NULL) && (parent->role == CSCH_ROLE_WIRE_NET)) csch_wirenet_post_remove_checks(sheet, parent, (how & CSCH_REM_UNDOABLE)); if ((how & CSCH_REM_DEL_EMPTY_PARENT) && (parent != NULL) && (parent->id2obj.used == 0) && (!csch_obj_is_deleted(&parent->hdr))) csch_cnc_remove_(sheet, &parent->hdr, how); uundo_unfreeze_serial(&sheet->undo); csch_undo_inc_serial(sheet); csch_sheet_set_changed(sheet, 1); return obj; } csch_chdr_t *csch_op_remove(csch_sheet_t *sheet, csch_chdr_t *obj) { return csch_cnc_remove_(sheet, obj, CSCH_REM_FROM_RTREE | CSCH_REM_FROM_PARENT | CSCH_REM_UNDOABLE | CSCH_REM_DEL_EMPTY_PARENT); } csch_chdr_t *csch_cnc_remove(csch_sheet_t *sheet, csch_chdr_t *obj) { return csch_cnc_remove_(sheet, obj, CSCH_REM_FROM_RTREE | CSCH_REM_FROM_PARENT); } csch_chdr_t *csch_op_create(csch_sheet_t *sheet, csch_cgrp_t *parent, csch_ctype_t type) { const csch_ops_t *op = csch_op_get(type); csch_chdr_t *obj; if ((op == NULL) || (op->create == NULL)) return NULL; obj = op->create(sheet, parent); if (obj != NULL) csch_op_inserted(sheet, parent, obj); return obj; } csch_chdr_t *csch_op_copy_into(csch_sheet_t *sheet, csch_cgrp_t *dst, csch_chdr_t *src) { csch_chdr_t *newo; TODO("undo problem: dup doesn't preserve oid and that may need to be undone"); newo = csch_cobj_dup(sheet, dst, src, 0, 1); csch_cobj_update(sheet, newo, 0); csch_op_inserted(sheet, dst, newo); return newo; } csch_chdr_t *csch_op_move_into(csch_sheet_t *sheet, csch_cgrp_t *dst, csch_chdr_t *src, int restore_conns, int undoable_conns) { csch_chdr_t *newo = NULL; vtp0_t conn_save; long n; /* save original connections before removing the object */ if (restore_conns) { conn_save = src->conn; src->conn.used = src->conn.alloced = 0; src->conn.array = NULL; } csch_op_remove(sheet, src); TODO("undo problem: dup doesn't preserve oid and that may need to be undone"); newo = csch_cobj_dup(sheet, dst, src, 0, 1); csch_cobj_update(sheet, newo, 0); csch_op_inserted(sheet, dst, newo); /* restore connections: remove old object and add new object to the same conns */ if (restore_conns) { for(n = 0; n < conn_save.used; n++) { csch_conn_t *orig_conn = conn_save.array[n]; csch_conn_t *existing = csch_conn_find_mergable(sheet, orig_conn, newo); if (existing) { /* corer case: removing the wire from orig_conn may have oprhaned orig_conn; this call takes care of removing that conn in an undoable manner */ csch_conn_auto_del_obj(sheet, orig_conn, src); /* if there is an existing "overlapping" conn in dst group, reuse that instead of our original conn; this happens when a wire from wirenet A is moved into wirenet B and the wire creates a new connection to a terminal that wirenet B already connected. In this case the wire should be removed from its own connection (orig_conn) and should be added to the existing connection of wirenet B. */ csch_conn_add_obj(sheet, existing, newo, undoable_conns); } else { /* replace object in conn: remove old object (src) and add new object (newo); this won't change the number of objects in orig_conn, it can't get orphaned */ csch_conn_del_obj(sheet, orig_conn, src, undoable_conns); csch_conn_add_obj(sheet, orig_conn, newo, undoable_conns); } } vtp0_uninit(&conn_save); } return newo; } void csch_op_merge_into(csch_sheet_t *sheet, csch_cgrp_t *dst, csch_cgrp_t *src) { htip_entry_t *e; /* always move the first, don't rely on htip_next() as items are being removed from src */ for(e = htip_first(&src->id2obj); e != NULL; e = htip_first(&src->id2obj)) csch_op_move_into(sheet, dst, e->value, 1, 1); csch_attrib_copy_all_undoable(sheet, dst, &dst->attr, &src->attr, 1); } void csch_op_merge_into_remember(csch_sheet_t *sheet, csch_cgrp_t *dst, csch_cgrp_t *src, vtp0_t *dst_news) { htip_entry_t *e; /* always move the first, don't rely on htip_next() as items are being removed from src */ for(e = htip_first(&src->id2obj); e != NULL; e = htip_first(&src->id2obj)) { void *newo = csch_op_move_into(sheet, dst, e->value, 1, 1); if (newo != NULL) vtp0_append(dst_news, newo); } csch_attrib_copy_all_undoable(sheet, dst, &dst->attr, &src->attr, 1); } int csch_isc_with_box(csch_chdr_t *obj, csch_rtree_box_t *box) { const csch_ops_t *op = csch_op_get(obj->type); if ((op == NULL) || (op->isc_with_box == NULL)) return 0; return op->isc_with_box(obj, box); } /* When moving or copying a floater: change dx and dy from sheet coords to parent group coords */ static void move_copy_floater_inverse(const csch_chdr_t *obj, csch_coord_t *dx, csch_coord_t *dy) { g2d_xform_t mx; g2d_vect_t v; /* optimization: noop on top objects */ if (obj->parent == &obj->sheet->direct) return; /* if there's no parent, there's no transformation possible (when copying to buffer) */ if (obj->parent == NULL) return; v.x = *dx; v.y = *dy; csch_cgrp_inverse_matrix(&mx, obj->parent); mx.v[2] = mx.v[5] = 0; /* drop translation, we are interested only in mirror/rot */ v = g2d_xform_vect2vect(mx, v); *dx = v.x; *dy = v.y; } void csch_move(csch_sheet_t *sheet, csch_chdr_t *obj, csch_coord_t dx, csch_coord_t dy, int undoable) { if ((dx != 0) || (dy != 0)) { const csch_ops_t *op = csch_op_get(obj->type); if ((op != NULL) && (op->move != NULL)) { uundo_freeze_serial(&sheet->undo); move_copy_floater_inverse(obj, &dx, &dy); if (!csch_grp_ref_child_moved(sheet, obj, dx, dy, undoable)) op->move(sheet, obj, dx, dy, undoable); if (undoable) { csch_conn_auto_recalc(sheet, obj); if (csch_obj_is_grp(obj)) { csch_cgrp_t *grp = (csch_cgrp_t *)obj; if (grp->role == CSCH_ROLE_WIRE_NET) csch_wirenet_recalc_junctions(sheet, grp); } } uundo_unfreeze_serial(&sheet->undo); uundo_inc_serial(&sheet->undo); if (undoable) csch_sheet_set_changed(sheet, 1); } } } void csch_inst2spec(csch_sheet_t *sheet, csch_chdr_t *obj, const csch_chdr_t *inst_from, int undoable) { const csch_ops_t *op = csch_op_get(obj->type); if ((op != NULL) && (op->inst2spec != NULL)) { uundo_freeze_serial(&sheet->undo); op->inst2spec(sheet, obj, inst_from, undoable); if (undoable) csch_conn_auto_recalc(sheet, obj); uundo_unfreeze_serial(&sheet->undo); uundo_inc_serial(&sheet->undo); } } void csch_copy(csch_sheet_t *sheet, csch_chdr_t *obj, csch_coord_t dx, csch_coord_t dy, int undoable) { if ((dx != 0) || (dy != 0)) { const csch_ops_t *op = csch_op_get(obj->type); if ((op != NULL) && (op->copy != NULL)) { /* csch_cobj_t *newo;*/ if (csch_obj_is_grp_ref_child(sheet, obj)) { rnd_message(RND_MSG_ERROR, "Can't copy group ref children (would change the referenced group)\n"); return; } uundo_freeze_serial(&sheet->undo); move_copy_floater_inverse(obj, &dx, &dy); /* newo = */ op->copy(sheet, obj, dx, dy, undoable); /* if (undoable && (newo != NULL)) csch_conn_auto_recalc(sheet, newo);*/ uundo_unfreeze_serial(&sheet->undo); uundo_inc_serial(&sheet->undo); } } } static void floater_origin(const csch_chdr_t *obj, csch_coord_t *x, csch_coord_t *y) { g2d_xform_t mx; g2d_vect_t v; /* optimization: noop on top objects */ if (obj->parent == &obj->sheet->direct) return; v.x = *x; v.y = *y; csch_cgrp_inverse_matrix(&mx, obj->parent); v = g2d_xform_vect2vect(mx, v); *x = v.x; *y = v.y; } void csch_rotate(csch_sheet_t *sheet, csch_chdr_t *obj, csch_coord_t rcx, csch_coord_t rcy, double da, int undoable) { da = fmod(da, 360); if (da != 0) { const csch_ops_t *op = csch_op_get(obj->type); if ((op != NULL) && (op->rotate != NULL)) { uundo_freeze_serial(&sheet->undo); floater_origin(obj, &rcx, &rcy); if (!csch_grp_ref_child_rotated(sheet, obj, rcx, rcy, da, undoable)) op->rotate(sheet, obj, rcx, rcy, da, undoable); if (undoable) csch_conn_auto_recalc(sheet, obj); uundo_unfreeze_serial(&sheet->undo); uundo_inc_serial(&sheet->undo); } } } void csch_rotate90(csch_sheet_t *sheet, csch_chdr_t *obj, csch_coord_t rcx, csch_coord_t rcy, int n, int undoable) { n = n & 0x3; if (n != 0) { const csch_ops_t *op = csch_op_get(obj->type); if ((op != NULL) && (op->rotate90 != NULL)) { uundo_freeze_serial(&sheet->undo); floater_origin(obj, &rcx, &rcy); if (!csch_grp_ref_child_rotated90(sheet, obj, rcx, rcy, n, undoable)) op->rotate90(sheet, obj, rcx, rcy, n, undoable); if (undoable) csch_conn_auto_recalc(sheet, obj); uundo_unfreeze_serial(&sheet->undo); uundo_inc_serial(&sheet->undo); } } } void csch_mirror(csch_sheet_t *sheet, csch_chdr_t *obj, csch_coord_t mcx, csch_coord_t mcy, int mirx, int miry, int undoable) { if (mirx || miry) { const csch_ops_t *op = csch_op_get(obj->type); if ((op != NULL) && (op->mirror != NULL)) { uundo_freeze_serial(&sheet->undo); floater_origin(obj, &mcx, &mcy); if (!csch_grp_ref_child_mirrored(sheet, obj, mcx, mcy, mirx, miry, undoable)) op->mirror(sheet, obj, mcx, mcy, mirx, miry, undoable); if (undoable) csch_conn_auto_recalc(sheet, obj); uundo_unfreeze_serial(&sheet->undo); uundo_inc_serial(&sheet->undo); } } }