/*
 *                            COPYRIGHT
 *
 *  sch-rnd - modular/flexible schematics editor - property editor plugin
 *  Copyright (C) 2022,2023 Tibor 'Igor2' Palinkas
 *  (Adapted from propsel code in pcb-rnd by the same author)
 *
 *  (Supported by NLnet NGI0 PET Fund in 2022)
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program 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 General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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 <libcschem/config.h>
#include <libcschem/concrete.h>
#include <libcschem/libcschem.h>

#include <ctype.h>
#include <genht/hash.h>
#include <genlist/gendlist.h>
#include <genvector/gds_char.h>

#include <librnd/hid/hid_dad.h>
#include <librnd/hid/hid_dad_tree.h>
#include <librnd/core/globalconst.h>
#include <librnd/core/actions.h>
#include <librnd/core/conf.h>
#include <librnd/core/conf_hid.h>
#include <librnd/core/event.h>

#include <libcschem/event.h>
#include <sch-rnd/dad.h>
#include <sch-rnd/draw.h>

#include "props.h"
#include "propsel.h"

extern const char csch_propedit_cookie[];

typedef struct{
	RND_DAD_DECL_NOINIT(dlg)
	csch_sheet_t *sheet;
	csch_propedit_t pe;
	int wtree, wfilter, wtype, wvals, wscope, wprev, whelptxt, wquickattr;
	int wabs[CSCH_PROPT_max], wedit[CSCH_PROPT_max];
	unsigned lock:1; /* do not refresh while editing */
	gdl_elem_t link;
} propdlg_t;

gdl_list_t propdlgs;

static void prop_refresh(propdlg_t *ctx);

static void propdlgclose_cb(void *caller_data, rnd_hid_attr_ev_t ev)
{
	propdlg_t *ctx = caller_data;
	gdl_remove(&propdlgs, ctx, link);
	csch_props_uninit(&ctx->pe);
	RND_DAD_FREE(ctx->dlg);
	free(ctx);
}

static void prop_filter_cb(void *hid_ctx, void *caller_data, rnd_hid_attribute_t *attr_ign)
{
	propdlg_t *ctx = caller_data;
	rnd_hid_attribute_t *attr, *attr_inp;
	rnd_hid_tree_t *tree;
	const char *text;
	int have_filter_text;

	attr = &ctx->dlg[ctx->wtree];
	attr_inp = &ctx->dlg[ctx->wfilter];
	tree = attr->wdata;
	text = attr_inp->val.str;
	have_filter_text = (text != NULL) && (*text != '\0');

	/* hide or unhide everything */
	rnd_dad_tree_hide_all(tree, &tree->rows, have_filter_text);

	if (have_filter_text) /* unhide hits and all their parents */
		rnd_dad_tree_unhide_filter(tree, &tree->rows, 0, text);

	rnd_dad_tree_update_hide(attr);
}

static void prop_sch2dlg(propdlg_t *ctx)
{
	rnd_hid_attribute_t *attr = &ctx->dlg[ctx->wtree];
	rnd_hid_tree_t *tree = attr->wdata;
	rnd_hid_row_t *r;
	htsp_entry_t *sorted, *e;
	char *cursor_path = NULL;

	/* remember cursor */
	r = rnd_dad_tree_get_selected(attr);
	if (r != NULL)
		cursor_path = rnd_strdup(r->path);

	rnd_dad_tree_clear(tree);

	csch_props_clear(&ctx->pe);
	csch_propsel_map_core(&ctx->pe);
	sorted = csch_props_sort(&ctx->pe);
	for(e = sorted; e->key != NULL; e++) {
		char *cell[6] = {NULL};
		csch_props_t *p = e->value;
		csch_propval_t com, min, max, avg;

		if ((p->type == CSCH_PROPT_STRING) || (p->type == CSCH_PROPT_COMMS) || (p->type == CSCH_PROPT_ENUM) || (p->type == CSCH_PROPT_UUID)) {
			csch_props_stat(&ctx->pe, p, &com, NULL, NULL, NULL);
		}
		else {
			csch_props_stat(&ctx->pe, p, &com, &min, &max, &avg);
			cell[2] = csch_propsel_printval(p, &min);
			cell[3] = csch_propsel_printval(p, &max);
			cell[4] = csch_propsel_printval(p, &avg);
		}

		cell[0] = rnd_strdup(e->key);
		cell[1] = csch_propsel_printval(p, &com);

		r = rnd_dad_tree_mkdirp(tree, cell[0], cell);
		r->user_data = e->key;
	}
	free(sorted);
	prop_filter_cb(ctx->dlg_hid_ctx, ctx, NULL);

	rnd_dad_tree_expcoll(attr, NULL, 1, 1);

	/* restore cursor */
	if (cursor_path != NULL) {
		rnd_hid_attr_val_t hv;
		hv.str = cursor_path;
		rnd_gui->attr_dlg_set_value(ctx->dlg_hid_ctx, ctx->wtree, &hv);
		free(cursor_path);
	}

	/* set scope */
	{
		rnd_hid_attr_val_t hv;
		gds_t scope;
		int inv;
		csch_oidpath_t *idp;

		gds_init(&scope);
		if (ctx->pe.use_sheet)
			gds_append_str(&scope, "sheet, ");
		if (ctx->pe.use_selection)
			gds_append_str(&scope, "selected objects, ");

		inv = 0;
		for(idp = csch_oidpath_list_first(&ctx->pe.objs); idp != NULL; idp = csch_oidpath_list_next(idp)) {
			csch_chdr_t *o = csch_oidpath_resolve(ctx->pe.sheet, idp);
			if (o != NULL)
				rnd_append_printf(&scope, "%s #%ld, ", csch_ctype_name(o->type), o->oid);
			else
				inv++;
		}
		if (inv > 0)
			rnd_append_printf(&scope, "%d invalid objects, ", inv);

		gds_truncate(&scope, gds_len(&scope)-2);

		hv.str = scope.array;
		rnd_gui->attr_dlg_set_value(ctx->dlg_hid_ctx, ctx->wscope, &hv);
		gds_uninit(&scope);
	}
}

static void (*help_expose)(rnd_hid_attribute_t *attrib, rnd_hid_preview_t *prv, rnd_hid_gc_t gc, rnd_hid_expose_ctx_t *e) = NULL;

static void prop_nil_expose_cb(rnd_hid_attribute_t *attrib, rnd_hid_preview_t *prv, rnd_hid_gc_t gc, rnd_hid_expose_ctx_t *e)
{
}

static void prop_prv_expose_cb(rnd_hid_attribute_t *attrib, rnd_hid_preview_t *prv, rnd_hid_gc_t gc, rnd_hid_expose_ctx_t *e)
{
	if (help_expose != NULL)
		help_expose(attrib, prv, gc, e);
}


static rnd_bool prop_prv_mouse_cb(rnd_hid_attribute_t *attrib, rnd_hid_preview_t *prv, rnd_hid_mouse_ev_t kind, rnd_coord_t x, rnd_coord_t y)
{
	return rnd_false; /* don't redraw */
}

static void prop_valedit_update(propdlg_t *ctx, csch_props_t *p, csch_propval_t *pv)
{
	rnd_hid_attr_val_t hv;
	minuid_str_t utmp;

	/* do not update the value if widget is numeric and the user wants a relative value */
	switch(p->type) {
		case CSCH_PROPT_COORD:
		case CSCH_PROPT_ANGLE:
		case CSCH_PROPT_DOUBLE:
		case CSCH_PROPT_INT:
			if (!ctx->dlg[ctx->wabs[p->type]].val.lng)
				return;
		default: break;
	}


	memset(&hv, 0, sizeof(hv));
	switch(p->type) {
		case CSCH_PROPT_STRING: hv.str = pv->string == NULL ? "" : pv->string; break;
		case CSCH_PROPT_COORD:  hv.crd = C2P(pv->coord); break;
		case CSCH_PROPT_ANGLE:  hv.dbl = pv->angle; break;
		case CSCH_PROPT_DOUBLE: hv.dbl = pv->d; break;
		case CSCH_PROPT_BOOL:
		case CSCH_PROPT_INT:    hv.lng = pv->i; break;
		case CSCH_PROPT_COMMS:  hv.str = pv->comms.str == NULL ? "" : pv->comms.str; break;
		case CSCH_PROPT_ENUM:   hv.str = p->enum_names[pv->i]; break;
		case CSCH_PROPT_ATTRARR:
			return; /* can't update directly */
		case CSCH_PROPT_UUID:  minuid_bin2str(utmp, pv->u); hv.str = utmp; break;

		case CSCH_PROPT_invalid:
		case CSCH_PROPT_max: return;
	}
	rnd_gui->attr_dlg_set_value(ctx->dlg_hid_ctx, ctx->wedit[p->type], &hv);
}

typedef struct {
	csch_propval_t *val;
	unsigned int cnt;
} pvsort_t;

static int sort_pv(const void *pv1_, const void *pv2_)
{
	const pvsort_t *pv1 = pv1_, *pv2 = pv2_;
	return pv1->cnt > pv2->cnt ? -1 : +1;
}

static void prop_vals_update(propdlg_t *ctx, csch_props_t *p)
{
	rnd_hid_attr_val_t hv;
	rnd_hid_attribute_t *attr = &ctx->dlg[ctx->wvals];
	rnd_hid_tree_t *tree = attr->wdata;
	htprop_entry_t *e;
	pvsort_t *pvs;
	char *cell[3] = {NULL, NULL, NULL};
	int n;

	rnd_dad_tree_clear(tree);

	if (p == NULL) { /* deselect or not found */
		hv.lng = 0;
		rnd_gui->attr_dlg_set_value(ctx->dlg_hid_ctx, ctx->wtype, &hv);
		return;
	}

	/* get a sorted list of all values, by frequency */
	pvs = malloc(sizeof(pvsort_t) * p->values.used);
	for (e = htprop_first(&p->values), n = 0; e; e = htprop_next(&p->values, e), n++) {
		pvs[n].val = &e->key;
		pvs[n].cnt = e->value;
	}
	qsort(pvs, p->values.used, sizeof(pvsort_t), sort_pv);

	for(n = 0; n < p->values.used; n++) {
		rnd_hid_row_t *r;
		cell[0] = rnd_strdup_printf("%ld", pvs[n].cnt);
		cell[1] = csch_propsel_printval(p, pvs[n].val);
		r = rnd_dad_tree_append(attr, NULL, cell);
		r->user_data = pvs[n].val;
	}

	hv.lng = p->type;
	rnd_gui->attr_dlg_set_value(ctx->dlg_hid_ctx, ctx->wtype, &hv);

	prop_valedit_update(ctx, p, pvs[0].val);

	free(pvs);
}

static void prop_select_node_cb(rnd_hid_attribute_t *attrib, void *hid_ctx, rnd_hid_row_t *row)
{
	rnd_design_t *hidlib = rnd_gui->get_dad_design(hid_ctx);
	rnd_hid_tree_t *tree = attrib->wdata;
	propdlg_t *ctx = tree->user_ctx;
	csch_props_t *p = NULL;
	void (*last_help)(rnd_hid_attribute_t *attrib, rnd_hid_preview_t *prv, rnd_hid_gc_t gc, rnd_hid_expose_ctx_t *e) = NULL;
	const char *helptxt = NULL;
	int enable_quick_attr = 0;

	last_help = help_expose;
	help_expose = NULL;

	if (row != NULL) {
		p = csch_props_get(&ctx->pe, row->user_data);

		/* update the help preview */
		if (strcmp(row->path, "p/grp/mirx") == 0)             helptxt = "Horizontal mirror the group.";
		else if (strcmp(row->path, "p/grp/miry") == 0)        helptxt = "Vertical mirror the group.";
		else if (strcmp(row->path, "p/grp/rot") == 0)         helptxt = "Rotate the group, counter\nclockwise. Mirroring inverts\ndirection of rotation.\nSpecified in degrees.";
		else if (strcmp(row->path, "p/grp/uuid") == 0)        helptxt = "Universally unique ID of\nthe group. Also\ncalled instance UID.\nGenerated using libminuid.";
		else if (strcmp(row->path, "p/grp/src_uuid") == 0)    helptxt = "The p/grp/uuid field of\nthe original group\nthis one is a copy of.";
		else if (strcmp(row->path, "p/floater") == 0)         helptxt = "When true, object can\nbe selected and moved\neven if it is in an atomic group.";
		else if (strcmp(row->path, "p/lock") == 0)            helptxt = "When true, object can\nNOT be selected and moved.";
		else if (strcmp(row->path, "p/stroke") == 0)          helptxt = "Name of the pen\nused for drawing the\noutline of the object.";
		else if (strcmp(row->path, "p/fill") == 0)            helptxt = "Name of the pen\nused for filling the\nobject. May be empty.";
		else if (strcmp(row->path, "p/has_fill") == 0)        helptxt = "If true, fill the\nobject using the fill_pen";
		else if (strcmp(row->path, "p/has_stroke") == 0)      helptxt = "If true, draw the\noutline of the object\nusing the stroke_pen";
		else if (strcmp(row->path, "p/text/dyntext") == 0)    helptxt = "If true, allow substitution of\n%patterns% in text field\nwhen rendering";
		else if (strcmp(row->path, "p/text/has_bbox") == 0)   helptxt = "If true, scale text\nso it best fits the bounding\nbox specified; else\nuse font with size (height)\nspecified in stroke pen.";
		else if (strcmp(row->path, "p/text/rot") == 0)        helptxt = "Rotate the text object, counter\nclockwise. Mirroring inverts\ndirection of rotation.\nSpecified in degrees.";
		else if (strcmp(row->path, "p/text/text") == 0)       helptxt = "String to print.\nIf dyntext is enabled,\n%patterns% are replaced\nbefore rendering.";
		else if (strcmp(row->path, "a/name") == 0)            helptxt = "Name of the object.\nTypical use cases:\nsymbol refdes,\nterminal name\n...";
		else if (strcmp(row->path, "a/role") == 0)            helptxt = "The program understood\nrole of the group.\nTypical values:\nsymbol\nterminal\nwire-net\n...";
	}

	if (last_help != help_expose) {
		rnd_hid_attr_val_t hv;
		hv.str = NULL;
		rnd_gui->attr_dlg_set_value(ctx->dlg_hid_ctx, ctx->wprev, &hv);
	}

	if (helptxt != NULL) {
		rnd_hid_attr_val_t hv;

		rnd_gui->attr_dlg_widget_hide(ctx->dlg_hid_ctx, ctx->wprev, 1);
		rnd_gui->attr_dlg_widget_hide(ctx->dlg_hid_ctx, ctx->whelptxt, 0);

		hv.str = helptxt;
		rnd_gui->attr_dlg_set_value(ctx->dlg_hid_ctx, ctx->whelptxt, &hv);
	}
	else {
		rnd_gui->attr_dlg_widget_hide(ctx->dlg_hid_ctx, ctx->whelptxt, 1);
		rnd_gui->attr_dlg_widget_hide(ctx->dlg_hid_ctx, ctx->wprev, 0);
	}


	if (ctx->wquickattr != 0) {
		if ((row != NULL) && (row->path != NULL) && (row->path[0] == 'a') && (row->path[1] == '/')) {
			const char *key = row->path+2;
			csch_oidpath_t *idp = prop_scope_single_obj(&ctx->pe);
			if (idp != NULL) {
				gds_t tmp = {0};
				gds_append_str(&tmp, "object:");
				csch_oidpath_to_str_append(&tmp, idp);
				enable_quick_attr = rnd_actionva(hidlib, "quickattreditable", tmp.array, key, NULL);
				gds_uninit(&tmp);
			}
		}
		rnd_gui->attr_dlg_widget_state(ctx->dlg_hid_ctx, ctx->wquickattr, enable_quick_attr);
	}

	prop_vals_update(ctx, p);
}


static void prop_data_cb(void *hid_ctx, void *caller_data, rnd_hid_attribute_t *attr, int force_update)
{
	propdlg_t *ctx = caller_data;
	rnd_hid_row_t *r = rnd_dad_tree_get_selected(&ctx->dlg[ctx->wtree]);
	csch_props_t *p = NULL;
	csch_propset_ctx_t sctx;

	if (r == NULL)
		return;
	p = csch_props_get(&ctx->pe, r->user_data);
	if (p == NULL)
		return;

	memset(&sctx, 0, sizeof(sctx));
	switch(p->type) {
		case CSCH_PROPT_invalid:
		case CSCH_PROPT_max:
			return;
		case CSCH_PROPT_STRING:
			sctx.s = ctx->dlg[ctx->wedit[p->type]].val.str;
			force_update = 1;
			break;
		case CSCH_PROPT_COORD:
			sctx.c = P2C(ctx->dlg[ctx->wedit[p->type]].val.crd);
			sctx.c_absolute = ctx->dlg[ctx->wabs[p->type]].val.lng;
			sctx.c_valid = 1;
			break;
		case CSCH_PROPT_ANGLE:
			sctx.d = ctx->dlg[ctx->wedit[p->type]].val.dbl;
			sctx.d_absolute = ctx->dlg[ctx->wabs[p->type]].val.lng;
			sctx.d_valid = 1;
			break;
		case CSCH_PROPT_DOUBLE:
			sctx.d = ctx->dlg[ctx->wedit[p->type]].val.dbl;
			sctx.d_absolute = ctx->dlg[ctx->wabs[p->type]].val.lng;
			sctx.d_valid = 1;
			break;
		case CSCH_PROPT_ENUM:
		case CSCH_PROPT_INT:
			sctx.c = ctx->dlg[ctx->wedit[p->type]].val.lng;
			sctx.c_absolute = ctx->dlg[ctx->wabs[p->type]].val.lng;
			sctx.c_valid = 1;
			break;
		case CSCH_PROPT_BOOL:
			sctx.c = ctx->dlg[ctx->wedit[p->type]].val.lng;
			sctx.c_absolute = ctx->dlg[ctx->wabs[p->type]].val.lng;
			sctx.c_valid = 1;
			break;
		case CSCH_PROPT_COMMS:
			sctx.comms = csch_comm_str(ctx->pe.sheet, ctx->dlg[ctx->wedit[p->type]].val.str, 1);
			force_update = 1;
			break;
		case CSCH_PROPT_UUID:
			minuid_gen(&csch_minuid, sctx.u);
			force_update = 1;
			break;
		case CSCH_PROPT_ATTRARR:
			break;
	}

	if (force_update || ctx->dlg[ctx->wabs[p->type]].val.lng) {
		ctx->lock = 1;
		csch_propsel_set(&ctx->pe, r->user_data, &sctx);
		prop_refresh(ctx);
		rnd_gui->invalidate_all(rnd_gui);
		ctx->lock = 0;
	}
}

static void prop_data_auto_cb(void *hid_ctx, void *caller_data, rnd_hid_attribute_t *attr)
{
	prop_data_cb(hid_ctx, caller_data, attr, 0);
}

static void prop_data_force_cb(void *hid_ctx, void *caller_data, rnd_hid_attribute_t *attr)
{
	prop_data_cb(hid_ctx, caller_data, attr, 1);
}

static void prop_change_comms_cb(void *hid_ctx, void *caller_data, rnd_hid_attribute_t *attr)
{
	propdlg_t *ctx = caller_data;
	rnd_hid_row_t *r = rnd_dad_tree_get_selected(&ctx->dlg[ctx->wtree]);
	csch_props_t *p = NULL;
	csch_sheet_t *sheet = ctx->pe.sheet;
	const char *pen_name = NULL;
	fgw_error_t rv;
	fgw_arg_t res, args[6];

	if (r == NULL)
		return;
	p = csch_props_get(&ctx->pe, r->user_data);
	if (p == NULL)
		return;

	switch(p->comms_type) {
		case CSCH_PROPCT_STRING:
			rnd_message(RND_MSG_ERROR, "Internal error: CSCH_PROPCT_STRING is not yet implemented in prop_change_comms_cb()\n");
			break;
		case CSCH_PROPCT_PEN:

			args[1].type = FGW_STR; args[1].val.str = "object:/2"; /* sheet->direct */
			args[2].type = FGW_STR; args[2].val.str = "modal";
			args[3].type = FGW_STR; args[3].val.str = "recursive";
			args[4].type = FGW_STR; args[4].val.str = "ret_name";
			args[5].type = FGW_STR; args[5].val.str = r->cell[1];

			rv = rnd_actionv_bin(&sheet->hidlib, "pendialog", &res, 6, args);
			if ((rv == 0) && ((res.type & FGW_STR) == FGW_STR))
				pen_name = res.val.str;

			if (pen_name != NULL) {
				rnd_hid_attr_val_t hv;
				hv.str = pen_name;
				rnd_gui->attr_dlg_set_value(ctx->dlg_hid_ctx, attr - ctx->dlg, &hv);
				prop_data_cb(hid_ctx, caller_data, attr, 1);
			}

			fgw_arg_free(&rnd_fgw, &res);
			break;
		default:
			rnd_message(RND_MSG_ERROR, "Internal error: invalid CSCH_PROPCT in prop_change_comms_cb()\n");
			break;
	}
}

static void prop_change_enum_cb(void *hid_ctx, void *caller_data, rnd_hid_attribute_t *attr)
{
	RND_DAD_DECL(dlg)
	propdlg_t *ctx = caller_data;
	rnd_hid_row_t *r = rnd_dad_tree_get_selected(&ctx->dlg[ctx->wtree]);
	csch_props_t *p = NULL;
/*	csch_sheet_t *sheet = ctx->pe.sheet;*/
	const char **en, *currv;
	int defv = -1, wval, failed;
	rnd_hid_dad_buttons_t clbtn[] = {{"Cancel", -1}, {"Apply selected", 0}, {NULL, 0}};

	if (r == NULL)
		return;
	p = csch_props_get(&ctx->pe, r->user_data);
	if (p == NULL)
		return;

	/* figure initial index value for the combo box */
	currv = ctx->dlg[ctx->wedit[8]].val.str;
	if (currv != NULL) {
		int n;
		for(n = 0, en = p->enum_names; *en != NULL; en++,n++) {
			if (strcmp(currv, *en) == 0) {
				defv = n;
				break;
			}
		}
	}

	RND_DAD_BEGIN_VBOX(dlg);
		RND_DAD_BEGIN_HBOX(dlg);
			RND_DAD_LABEL(dlg, "Select a value for");
			RND_DAD_LABEL(dlg, r->cell[0]);
		RND_DAD_END(dlg);
		RND_DAD_ENUM(dlg, p->enum_names);
			wval = RND_DAD_CURRENT(dlg);
			RND_DAD_DEFAULT_NUM(dlg, defv);
		RND_DAD_BUTTON_CLOSES(dlg, clbtn);
	RND_DAD_END(dlg);

	RND_DAD_AUTORUN("propedit_enum", dlg, "Propedit: edit enum value", NULL, failed);

	if (failed == 0) {
		csch_propsel_set_str(&ctx->pe, r->user_data, p->enum_names[dlg[wval].val.lng]);
		prop_refresh(ctx);
	}
	RND_DAD_FREE(dlg);

	return;
}

static void prop_change_attrarr_cb(void *hid_ctx, void *caller_data, rnd_hid_attribute_t *attr)
{
	propdlg_t *ctx = caller_data;
	rnd_hid_row_t *r = rnd_dad_tree_get_selected(&ctx->dlg[ctx->wtree]);
/*	csch_sheet_t *sheet = ctx->pe.sheet;*/
	csch_oidpath_t *idp;
	rnd_design_t *hl = rnd_gui->get_dad_design(hid_ctx);


	idp = prop_scope_single_obj(&ctx->pe);
	if (idp != NULL) {
		gds_t tmp = {0};
		gds_append_str(&tmp, "object:");
		csch_oidpath_to_str_append(&tmp, idp);
		rnd_actionva(hl, "AttributeDialog", tmp.array, r->cell[0], NULL);
		gds_uninit(&tmp);
	}
	else
		rnd_message(RND_MSG_ERROR, "Attribute-editing array is supported only for single-object propedit\nThis propedit has a scope of multiple objects.\n");
}

static void prop_attrdlg_cb(void *hid_ctx, void *caller_data, rnd_hid_attribute_t *attr)
{
	propdlg_t *ctx = caller_data;
	csch_oidpath_t *idp;
	rnd_design_t *hl = rnd_gui->get_dad_design(hid_ctx);

	idp = prop_scope_single_obj(&ctx->pe);
	if (idp != NULL) {
		gds_t tmp = {0};
		gds_append_str(&tmp, "object:");
		csch_oidpath_to_str_append(&tmp, idp);
		rnd_actionva(hl, "AttributeDialog", tmp.array, NULL);
		gds_uninit(&tmp);
	}
	else
		rnd_message(RND_MSG_ERROR, "Attribute-editing array is supported only for single-object propedit\nThis propedit has a scope of multiple objects.\n");
}

static void prop_quickattr_cb(void *hid_ctx, void *caller_data, rnd_hid_attribute_t *attr)
{
	rnd_design_t *hidlib = rnd_gui->get_dad_design(hid_ctx);
	propdlg_t *ctx = caller_data;
	csch_oidpath_t *idp;

	idp = prop_scope_single_obj(&ctx->pe);
	if (idp != NULL) {
		csch_chdr_t *obj = csch_oidpath_resolve(ctx->sheet, idp);
		if (obj != NULL) {
			rnd_hid_row_t *r = rnd_dad_tree_get_selected(&ctx->dlg[ctx->wtree]);
			const char *key = r->cell[0];
			gds_t tmp = {0};

			gds_append_str(&tmp, "object:");
			csch_oidpath_to_str_append(&tmp, idp);
			rnd_actionva(hidlib, "quickattr", tmp.array, key, NULL);
			gds_uninit(&tmp);
		}
		else
			rnd_message(RND_MSG_ERROR, "Internal error: object not found in prop_quickattr_cb().\n");
	}
	else
		rnd_message(RND_MSG_ERROR, "Attribute-editing array is supported only for single-object propedit\nThis propedit has a scope of multiple objects.\n");
}

static void prop_attr_enter_pressed_cb(void *hid_ctx, void *caller_data, rnd_hid_attribute_t *attr)
{
	rnd_dad_retovr_t **retovr = caller_data;
	rnd_hid_dad_close(hid_ctx, *retovr, 0);
}

static void prop_add_cb(void *hid_ctx, void *caller_data, rnd_hid_attribute_t *attr)
{
	RND_DAD_DECL(dlg)
	propdlg_t *ctx = caller_data;
	const char *key;
	int wkey, wval, failed;
	rnd_hid_dad_buttons_t clbtn[] = {{"Cancel", -1}, {"OK", 0}, {NULL, 0}};

	RND_DAD_BEGIN_VBOX(dlg);
		RND_DAD_BEGIN_TABLE(dlg, 2);
			RND_DAD_COMPFLAG(dlg, RND_HATF_EXPFILL);
			RND_DAD_LABEL(dlg, "Attribute key:");
			RND_DAD_STRING(dlg);
				wkey = RND_DAD_CURRENT(dlg);
			RND_DAD_LABEL(dlg, "Attribute value:");
			RND_DAD_STRING(dlg);
				wval = RND_DAD_CURRENT(dlg);
				RND_DAD_ENTER_CB(dlg, prop_attr_enter_pressed_cb);
		RND_DAD_END(dlg);
		RND_DAD_BUTTON_CLOSES(dlg, clbtn);
	RND_DAD_END(dlg);
	RND_DAD_AUTORUN("propedit_add", dlg, "Propedit: add new attribute", &dlg_ret_override, failed);

	key = dlg[wkey].val.str;
	if (key == NULL) key = "";
	while(isspace(*key)) key++;

	if ((failed == 0) && (*key != '\0')) {
		char *path = rnd_strdup_printf("a/%s", key);
		ctx->lock = 1;
		csch_propsel_set_str(&ctx->pe, path, dlg[wval].val.str);
		free(path);
		prop_refresh(ctx);
		ctx->lock = 0;
	}
	RND_DAD_FREE(dlg);
}

static void prop_del_cb(void *hid_ctx, void *caller_data, rnd_hid_attribute_t *attr)
{
	propdlg_t *ctx = caller_data;
	rnd_hid_row_t *r = rnd_dad_tree_get_selected(&ctx->dlg[ctx->wtree]);
	if (r == NULL) {
		rnd_message(RND_MSG_ERROR, "can not delete: no attribute selected\n");
		return;
	}
	if (r->path[0] != 'a') {
		rnd_message(RND_MSG_ERROR, "Only atributes (a/ subtree) can be deleted.\n");
		return;
	}

	ctx->lock = 1;
	if (csch_propsel_del(&ctx->pe, r->path) < 1) {
		ctx->lock = 0;
		rnd_message(RND_MSG_ERROR, "Failed to remove the attribute from any object.\n");
		return;
	}
	prop_refresh(ctx);
	ctx->lock = 0;
}

static void prop_preset_cb(void *hid_ctx, void *caller_data, rnd_hid_attribute_t *attr)
{
	propdlg_t *ctx = caller_data;
	rnd_hid_row_t *rv = rnd_dad_tree_get_selected(&ctx->dlg[ctx->wvals]);
	rnd_hid_row_t *rp = rnd_dad_tree_get_selected(&ctx->dlg[ctx->wtree]);
	csch_props_t *p;

	if ((rv == NULL) || (rv->user_data == NULL) || (rp == NULL))
		return;

	p = csch_props_get(&ctx->pe, rp->user_data);
	if (p != NULL)
		prop_valedit_update(ctx, p, rv->user_data);
}


static void prop_refresh_cb(void *hid_ctx, void *caller_data, rnd_hid_attribute_t *attr)
{
	prop_refresh((propdlg_t *)caller_data);
}


static void prop_refresh(propdlg_t *ctx)
{
	rnd_hid_attribute_t *attr = &ctx->dlg[ctx->wtree];
	prop_sch2dlg(ctx);
	prop_select_node_cb(attr, ctx->dlg_hid_ctx, rnd_dad_tree_get_selected(attr));
}


static void build_propval(propdlg_t *ctx)
{
	static const char *type_tabs[] = {"none", "string", "coord", "angle", "double", "int", NULL};
	static const char *abshelp = "When unticked each apply is a relative change added to\nthe current value of each object";

	RND_DAD_BEGIN_TABBED(ctx->dlg, type_tabs);
		RND_DAD_COMPFLAG(ctx->dlg, RND_HATF_HIDE_TABLAB);
		ctx->wtype = RND_DAD_CURRENT(ctx->dlg);
		RND_DAD_BEGIN_VBOX(ctx->dlg);
			RND_DAD_LABEL(ctx->dlg, "(nothing to edit)");
				ctx->wabs[0] = 0;
				ctx->wedit[0] = 0;
		RND_DAD_END(ctx->dlg);
		RND_DAD_BEGIN_VBOX(ctx->dlg);
			RND_DAD_LABEL(ctx->dlg, "Data type: string");
			RND_DAD_STRING(ctx->dlg);
				ctx->wedit[1] = RND_DAD_CURRENT(ctx->dlg);
				RND_DAD_ENTER_CB(ctx->dlg, prop_data_auto_cb);
			RND_DAD_BEGIN_HBOX(ctx->dlg);
				ctx->wabs[1] = 0;
				RND_DAD_BEGIN_HBOX(ctx->dlg);
					RND_DAD_COMPFLAG(ctx->dlg, RND_HATF_EXPFILL);
				RND_DAD_END(ctx->dlg);
				RND_DAD_BUTTON(ctx->dlg, "apply");
					RND_DAD_CHANGE_CB(ctx->dlg, prop_data_force_cb);
			RND_DAD_END(ctx->dlg);
		RND_DAD_END(ctx->dlg);
		RND_DAD_BEGIN_VBOX(ctx->dlg);
			RND_DAD_LABEL(ctx->dlg, "Data type: coord");
			RND_DAD_CSCH_COORD(ctx->dlg);
				ctx->wedit[2] = RND_DAD_CURRENT(ctx->dlg);
				RND_DAD_ENTER_CB(ctx->dlg, prop_data_auto_cb);
			RND_DAD_BEGIN_HBOX(ctx->dlg);
				RND_DAD_LABEL(ctx->dlg, "abs");
				RND_DAD_BOOL(ctx->dlg);
					ctx->wabs[2] = RND_DAD_CURRENT(ctx->dlg);
					RND_DAD_HELP(ctx->dlg, abshelp);
				RND_DAD_BEGIN_HBOX(ctx->dlg);
					RND_DAD_COMPFLAG(ctx->dlg, RND_HATF_EXPFILL);
				RND_DAD_END(ctx->dlg);
				RND_DAD_BUTTON(ctx->dlg, "apply");
					RND_DAD_CHANGE_CB(ctx->dlg, prop_data_force_cb);
			RND_DAD_END(ctx->dlg);
		RND_DAD_END(ctx->dlg);
		RND_DAD_BEGIN_VBOX(ctx->dlg);
			RND_DAD_LABEL(ctx->dlg, "Data type: angle");
			RND_DAD_REAL(ctx->dlg);
				ctx->wedit[3] = RND_DAD_CURRENT(ctx->dlg);
				RND_DAD_MINMAX(ctx->dlg, -360.0, +360.0);
				RND_DAD_ENTER_CB(ctx->dlg, prop_data_auto_cb);
			RND_DAD_BEGIN_HBOX(ctx->dlg);
				RND_DAD_LABEL(ctx->dlg, "abs");
				RND_DAD_BOOL(ctx->dlg);
					ctx->wabs[3] = RND_DAD_CURRENT(ctx->dlg);
					RND_DAD_HELP(ctx->dlg, abshelp);
				RND_DAD_BEGIN_HBOX(ctx->dlg);
					RND_DAD_COMPFLAG(ctx->dlg, RND_HATF_EXPFILL);
				RND_DAD_END(ctx->dlg);
				RND_DAD_BUTTON(ctx->dlg, "apply");
					RND_DAD_CHANGE_CB(ctx->dlg, prop_data_force_cb);
			RND_DAD_END(ctx->dlg);
		RND_DAD_END(ctx->dlg);
		RND_DAD_BEGIN_VBOX(ctx->dlg);
			RND_DAD_LABEL(ctx->dlg, "Data type: double");
			RND_DAD_REAL(ctx->dlg);
				ctx->wedit[4] = RND_DAD_CURRENT(ctx->dlg);
				RND_DAD_MINMAX(ctx->dlg, -1000, +1000);
				RND_DAD_ENTER_CB(ctx->dlg, prop_data_auto_cb);
			RND_DAD_BEGIN_HBOX(ctx->dlg);
				RND_DAD_LABEL(ctx->dlg, "abs");
				RND_DAD_BOOL(ctx->dlg);
					ctx->wabs[4] = RND_DAD_CURRENT(ctx->dlg);
					RND_DAD_HELP(ctx->dlg, abshelp);
				RND_DAD_BEGIN_HBOX(ctx->dlg);
					RND_DAD_COMPFLAG(ctx->dlg, RND_HATF_EXPFILL);
				RND_DAD_END(ctx->dlg);
				RND_DAD_BUTTON(ctx->dlg, "apply");
					RND_DAD_CHANGE_CB(ctx->dlg, prop_data_force_cb);
			RND_DAD_END(ctx->dlg);
		RND_DAD_END(ctx->dlg);


		RND_DAD_BEGIN_VBOX(ctx->dlg);
			RND_DAD_LABEL(ctx->dlg, "Data type: int");
			RND_DAD_INTEGER(ctx->dlg);
				ctx->wedit[5] = RND_DAD_CURRENT(ctx->dlg);
				RND_DAD_ENTER_CB(ctx->dlg, prop_data_auto_cb);
			RND_DAD_MINMAX(ctx->dlg, -(1<<30), 1<<30);
			RND_DAD_BEGIN_HBOX(ctx->dlg);
				RND_DAD_LABEL(ctx->dlg, "abs");
				RND_DAD_BOOL(ctx->dlg);
					ctx->wabs[5] = RND_DAD_CURRENT(ctx->dlg);
					RND_DAD_HELP(ctx->dlg, abshelp);
				RND_DAD_BEGIN_HBOX(ctx->dlg);
					RND_DAD_COMPFLAG(ctx->dlg, RND_HATF_EXPFILL);
				RND_DAD_END(ctx->dlg);
				RND_DAD_BUTTON(ctx->dlg, "apply");
					RND_DAD_CHANGE_CB(ctx->dlg, prop_data_force_cb);
			RND_DAD_END(ctx->dlg);
		RND_DAD_END(ctx->dlg);

		RND_DAD_BEGIN_VBOX(ctx->dlg);
			RND_DAD_LABEL(ctx->dlg, "Data type: boolean");
			RND_DAD_BOOL(ctx->dlg);
				ctx->wedit[6] = RND_DAD_CURRENT(ctx->dlg);
			RND_DAD_BEGIN_HBOX(ctx->dlg);
				ctx->wabs[6] = 0;
				RND_DAD_BEGIN_HBOX(ctx->dlg);
					RND_DAD_COMPFLAG(ctx->dlg, RND_HATF_EXPFILL);
				RND_DAD_END(ctx->dlg);
				RND_DAD_BUTTON(ctx->dlg, "apply");
					RND_DAD_CHANGE_CB(ctx->dlg, prop_data_force_cb);
			RND_DAD_END(ctx->dlg);
		RND_DAD_END(ctx->dlg);

		RND_DAD_BEGIN_VBOX(ctx->dlg);
			RND_DAD_LABEL(ctx->dlg, "Data type: common string");
			RND_DAD_BUTTON(ctx->dlg, "<TODO: pen>");
				ctx->wedit[7] = RND_DAD_CURRENT(ctx->dlg);
				RND_DAD_CHANGE_CB(ctx->dlg, prop_change_comms_cb);
			RND_DAD_BEGIN_HBOX(ctx->dlg);
				ctx->wabs[7] = 0;
				RND_DAD_BEGIN_HBOX(ctx->dlg);
					RND_DAD_COMPFLAG(ctx->dlg, RND_HATF_EXPFILL);
				RND_DAD_END(ctx->dlg);
				RND_DAD_BUTTON(ctx->dlg, "apply");
					RND_DAD_CHANGE_CB(ctx->dlg, prop_data_force_cb);
			RND_DAD_END(ctx->dlg);
		RND_DAD_END(ctx->dlg);

		RND_DAD_BEGIN_VBOX(ctx->dlg);
			RND_DAD_LABEL(ctx->dlg, "Data type: enum");
			RND_DAD_BUTTON(ctx->dlg, "<TODO: enum>");
				ctx->wedit[8] = RND_DAD_CURRENT(ctx->dlg);
				RND_DAD_CHANGE_CB(ctx->dlg, prop_change_enum_cb);
			RND_DAD_BEGIN_HBOX(ctx->dlg);
				ctx->wabs[8] = 0;
				RND_DAD_BEGIN_HBOX(ctx->dlg);
					RND_DAD_COMPFLAG(ctx->dlg, RND_HATF_EXPFILL);
				RND_DAD_END(ctx->dlg);
				RND_DAD_BUTTON(ctx->dlg, "apply");
					RND_DAD_CHANGE_CB(ctx->dlg, prop_data_force_cb);
			RND_DAD_END(ctx->dlg);
		RND_DAD_END(ctx->dlg);

		RND_DAD_BEGIN_VBOX(ctx->dlg);
			RND_DAD_LABEL(ctx->dlg, "Data type: array");
			RND_DAD_BUTTON(ctx->dlg, "Edit...");
				ctx->wedit[9] = RND_DAD_CURRENT(ctx->dlg);
				RND_DAD_CHANGE_CB(ctx->dlg, prop_change_attrarr_cb);
		RND_DAD_END(ctx->dlg);


		RND_DAD_BEGIN_VBOX(ctx->dlg);
			RND_DAD_LABEL(ctx->dlg, "Data type: UUID");
			RND_DAD_BUTTON(ctx->dlg, "Replace");
				ctx->wedit[10] = RND_DAD_CURRENT(ctx->dlg);
				RND_DAD_CHANGE_CB(ctx->dlg, prop_data_force_cb);
		RND_DAD_END(ctx->dlg);


	RND_DAD_END(ctx->dlg);
}

static void csch_dlg_propdlg(propdlg_t *ctx)
{
	const char *hdr[] = {"property", "common", "min", "max", "avg", NULL};
	const char *hdr_val[] = {"use", "values"};
	rnd_hid_dad_buttons_t clbtn[] = {{"Close", 0}, {NULL, 0}};
	static rnd_box_t prvbb = {0, 0, RND_MM_TO_COORD(10), RND_MM_TO_COORD(10)};
	int n;
	rnd_hid_attr_val_t hv;

	RND_DAD_BEGIN_VBOX(ctx->dlg);
		RND_DAD_COMPFLAG(ctx->dlg, RND_HATF_EXPFILL);
		RND_DAD_BEGIN_HPANE(ctx->dlg, "left-right");

			/* left: property tree and filter */
			RND_DAD_BEGIN_VBOX(ctx->dlg);
				RND_DAD_COMPFLAG(ctx->dlg, RND_HATF_EXPFILL);
				RND_DAD_TREE(ctx->dlg, 5, 1, hdr);
					RND_DAD_COMPFLAG(ctx->dlg, RND_HATF_EXPFILL | RND_HATF_SCROLL);
					ctx->wtree = RND_DAD_CURRENT(ctx->dlg);
					RND_DAD_TREE_SET_CB(ctx->dlg, selected_cb, prop_select_node_cb);
					RND_DAD_TREE_SET_CB(ctx->dlg, ctx, ctx);
				RND_DAD_BEGIN_HBOX(ctx->dlg);
					RND_DAD_STRING(ctx->dlg);
						RND_DAD_HELP(ctx->dlg, "Filter text:\nlist properties with\nmatching name only");
						RND_DAD_CHANGE_CB(ctx->dlg, prop_filter_cb);
						ctx->wfilter = RND_DAD_CURRENT(ctx->dlg);
					RND_DAD_BUTTON(ctx->dlg, "add");
						RND_DAD_CHANGE_CB(ctx->dlg, prop_add_cb);
						RND_DAD_HELP(ctx->dlg, "Create a new attribute\n(in the a/ subtree)");
					RND_DAD_BUTTON(ctx->dlg, "del");
						RND_DAD_CHANGE_CB(ctx->dlg, prop_del_cb);
						RND_DAD_HELP(ctx->dlg, "Remove the selected attribute\n(from the a/ subtree)");
					RND_DAD_BUTTON(ctx->dlg, "rfr");
						RND_DAD_CHANGE_CB(ctx->dlg, prop_refresh_cb);
						RND_DAD_HELP(ctx->dlg, "Refresh: rebuild the tree\nupdating all values from the sheet");
				RND_DAD_END(ctx->dlg);
			RND_DAD_END(ctx->dlg);

			/* right: preview and per type edit */
			RND_DAD_BEGIN_VBOX(ctx->dlg);
				RND_DAD_COMPFLAG(ctx->dlg, RND_HATF_EXPFILL);
				RND_DAD_BEGIN_HBOX(ctx->dlg);
					RND_DAD_PREVIEW(ctx->dlg, prop_prv_expose_cb, prop_prv_mouse_cb, NULL, NULL, &prvbb, 150, 150, ctx);
						ctx->wprev = RND_DAD_CURRENT(ctx->dlg);
					RND_DAD_LABEL(ctx->dlg, "help");
						ctx->whelptxt = RND_DAD_CURRENT(ctx->dlg);
					RND_DAD_PREVIEW(ctx->dlg, prop_nil_expose_cb, NULL, NULL, NULL, &prvbb, 1, 150, ctx); /* placeholder */
				RND_DAD_END(ctx->dlg);
				RND_DAD_LABEL(ctx->dlg, "<scope>");
					ctx->wscope = RND_DAD_CURRENT(ctx->dlg);
					RND_DAD_HELP(ctx->dlg, "Scope: list of objects affected");
				RND_DAD_BEGIN_VBOX(ctx->dlg);
					RND_DAD_COMPFLAG(ctx->dlg, RND_HATF_EXPFILL | RND_HATF_SCROLL);
					RND_DAD_TREE(ctx->dlg, 2, 0, hdr_val);
						ctx->wvals = RND_DAD_CURRENT(ctx->dlg);
						RND_DAD_CHANGE_CB(ctx->dlg, prop_preset_cb);
				RND_DAD_END(ctx->dlg);
				RND_DAD_BEGIN_VBOX(ctx->dlg);
					build_propval(ctx);
				RND_DAD_END(ctx->dlg);

				RND_DAD_BEGIN_HBOX(ctx->dlg);
					if (prop_scope_single_obj(&ctx->pe)) {
						RND_DAD_BUTTON(ctx->dlg, "Attr. Dlg.");
							RND_DAD_CHANGE_CB(ctx->dlg, prop_attrdlg_cb);
							RND_DAD_HELP(ctx->dlg, "Invoke the attribute editor dialog box for this object");
						RND_DAD_BUTTON(ctx->dlg, "Quick edit");
							ctx->wquickattr = RND_DAD_CURRENT(ctx->dlg);
							RND_DAD_CHANGE_CB(ctx->dlg, prop_quickattr_cb);
							RND_DAD_HELP(ctx->dlg, "Invoke the assisted/special attribute editor for the attribute");
					}
					/* spring */
					RND_DAD_BEGIN_HBOX(ctx->dlg);
						RND_DAD_COMPFLAG(ctx->dlg, RND_HATF_EXPFILL);
					RND_DAD_END(ctx->dlg);
					RND_DAD_BUTTON_CLOSES(ctx->dlg, clbtn);
				RND_DAD_END(ctx->dlg);
			RND_DAD_END(ctx->dlg);
		RND_DAD_END(ctx->dlg);
	RND_DAD_END(ctx->dlg);

	RND_DAD_NEW("propedit", ctx->dlg, "Property editor", ctx, rnd_false, propdlgclose_cb); /* type=local */

	prop_refresh(ctx);
	gdl_append(&propdlgs, ctx, link);

	/* default all abs */
	hv.lng = 1;
	for(n = 0; n < CSCH_PROPT_max; n++)
		rnd_gui->attr_dlg_set_value(ctx->dlg_hid_ctx, ctx->wabs[n], &hv);
}

extern int prop_scope_add(csch_propedit_t *pe, const char *cmd, int quiet);

const char csch_acts_propedit[] = "propedit(object[:id]|sheet|selection)\n";
const char csch_acth_propedit[] = "";
fgw_error_t csch_act_propedit(fgw_arg_t *res, int argc, fgw_arg_t *argv)
{
	rnd_design_t *hl = RND_ACT_DESIGN;
	csch_sheet_t *sheet = (csch_sheet_t *)hl;
	propdlg_t *ctx = calloc(sizeof(propdlg_t), 1);
	int a, r;

	ctx->sheet = sheet;
	csch_props_init(&ctx->pe, sheet, &sheet->direct);

	if (argc > 1) {
		for(a = 1; a < argc; a++) {
			const char *cmd;
			RND_ACT_CONVARG(a, FGW_STR, propedit, cmd = argv[a].val.str);
			r = prop_scope_add(&ctx->pe, cmd, 0);
			if (r != 0)
				return r;
		}
	}
	else
		ctx->pe.use_selection = 1;

	csch_dlg_propdlg(ctx);
	return 0;
}

static void propdlg_unit_change(rnd_conf_native_t *cfg, int arr_idx, void *user_data)
{
	propdlg_t *ctx;
	gdl_iterator_t it;

	gdl_foreach(&propdlgs, &it, ctx) {
		prop_sch2dlg(ctx);
	}
}

static void propedit_sheet_edit(rnd_design_t *hidlib, void *user_data, int argc, rnd_event_arg_t argv[])
{
	propdlg_t *pd;

	for(pd = gdl_first(&propdlgs); pd != NULL; pd = gdl_next(&propdlgs, pd))
		if ((pd->pe.use_selection || (pd->pe.objs.lst.length != 0)) && !pd->lock)
			prop_refresh(pd);
}

static void propedit_sheet_preunload(rnd_design_t *hidlib, void *user_data, int argc, rnd_event_arg_t argv[])
{
	csch_sheet_t *sheet = (csch_sheet_t *)hidlib;
	propdlg_t *n, *next;
	rnd_dad_retovr_t retovr = {0};

	for(n = gdl_first(&propdlgs); n != NULL; n = next) {
		next = gdl_next(&propdlgs, n);
		if (n->sheet == sheet)
			rnd_hid_dad_close(n->dlg_hid_ctx, &retovr, 0);
	}
}


static rnd_conf_hid_id_t propdlg_conf_id;
static const char *propdlg_cookie = "propdlg";
void csch_propdlg_init(void)
{
	static rnd_conf_hid_callbacks_t cbs;
	rnd_conf_native_t *n = rnd_conf_get_field("editor/grid_unit");
	propdlg_conf_id = rnd_conf_hid_reg(propdlg_cookie, NULL);

	if (n != NULL) {
		cbs.val_change_post = propdlg_unit_change;
		rnd_conf_hid_set_cb(n, propdlg_conf_id, &cbs);
	}

	rnd_event_bind(CSCH_EVENT_SHEET_EDITED, propedit_sheet_edit, NULL, csch_propedit_cookie);
	rnd_event_bind(CSCH_EVENT_SELECTION_CHANGED, propedit_sheet_edit, NULL, csch_propedit_cookie);
	rnd_event_bind(CSCH_EVENT_SHEET_PREUNLOAD, propedit_sheet_preunload, NULL, csch_propedit_cookie);
}

void csch_propdlg_uninit(void)
{
	rnd_conf_hid_unreg(propdlg_cookie);
	rnd_event_unbind_allcookie(csch_propedit_cookie);
}
