/*
 *                            COPYRIGHT
 *
 *  sch-rnd - modular/flexible schematics editor - GUI - attribute dialog
 *  Copyright (C) 2020,2022,2023 Tibor 'Igor2' Palinkas
 *
 *  (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., 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 <libcschem/config.h>

#include <string.h>
#include <genht/hash.h>
#include <genlist/gendlist.h>

#include <librnd/core/event.h>
#include <librnd/core/actions.h>
#include <librnd/core/hidlib.h>
#include <librnd/core/rnd_conf.h>
#include <librnd/hid/hid_dad.h>
#include <librnd/hid/hid_dad_tree.h>
#include <librnd/core/compat_misc.h>
#include <librnd/core/compat_fs.h>
#include <librnd/core/globalconst.h>

#include <libcschem/search.h>
#include <libcschem/abstract.h>
#include <libcschem/cnc_grp.h>
#include <libcschem/util_grp.h>
#include <sch-rnd/draw.h>
#include <sch-rnd/crosshair.h>
#include <sch-rnd/search.h>

#include "abst_attr.h"
#include "quick_attr.h"
#include "dlg_attrib.h"

typedef struct {
	RND_DAD_DECL_NOINIT(dlg)
	abst_attrdlg_ctx_t right;

	csch_sheet_t *sheet;
	csch_cgrp_t *obj;
	csch_coord_t *ch_xy;    /* crosshair x;y coord at the moment of invocation or NULL if not available */
	csch_coord_t ch_xy_[2]; /* backing store for ch_xy */
	char *name;
	const char *last_attr_key;
	int modal;

	int wattrs, wstr_sect, warr_sect, wkey, wprio, wprio2, wval_str, wval_tree;
	int wconv_arr, wconv_str;
	int wvaledit, wassist, wfloater;

	int refresh_lock; /* when non-zero ignore obj attr change events */

	gdl_elem_t link;
} attrdlg_ctx_t;

static gdl_list_t attrdlgs;

typedef struct {
	const char *key;
	enum { AT_ENUM, AT_STR, AT_ARR } type;
	const char **vals; /* for AT_ENUM */
} known_attr_t;

static const char *role_vals[] = {"symbol", "wire-net", "terminal", NULL};
static const known_attr_t known_attrs[] = {
	{"name", AT_STR, NULL},
	{"purpose", AT_STR, NULL},
	{"role", AT_ENUM, role_vals},
	{NULL, AT_STR, NULL}
};

static char *known_attr_render_cell(const csch_attrib_t *a)
{
	const known_attr_t *k;
	const char **v;

	for(k = known_attrs; k->key != NULL; k++) {
		if (strcmp(a->key, k->key) == 0) {
			if (a->val != NULL) {
				switch(k->type) {
					case AT_ENUM:
						for(v = k->vals; *v != NULL; v++) {
							if (strcmp(*v, a->val) == 0)
								return rnd_strdup("*");
						}
						return rnd_strdup("?"); /* unknown value */
						break;
					case AT_ARR:
						return rnd_strdup("!"); /* attrib is str, we expected an arr */
					case AT_STR:
						return rnd_strdup("*");
				}
			}
			else {
				switch(k->type) {
					case AT_ENUM:
					case AT_STR:
						return rnd_strdup("!"); /* attrib is arr, we expected a string */
					case AT_ARR:
						return rnd_strdup("*");
				}
			}
		}
	}
	return rnd_strdup(" ");
}

static void attrdlg_close_cb(void *caller_data, rnd_hid_attr_ev_t ev)
{
	attrdlg_ctx_t *ctx = caller_data;
	gdl_remove(&attrdlgs, ctx, link);
	RND_DAD_FREE(ctx->dlg);
	free(ctx->name);
	if (!ctx->modal)
		free(ctx);
}

/* load attribute array strings in arr value tree qidget */
static void attr2dlg_arr(attrdlg_ctx_t *ctx, csch_attrib_t *a)
{
	rnd_hid_attribute_t *attr;
	rnd_hid_tree_t *tree;
	rnd_hid_row_t *r;
	char *cursor_path = NULL;
	char *cell[2];
	long n;

	attr = &ctx->dlg[ctx->wval_tree];
	tree = attr->wdata;

	/* remember cursor */
	r = rnd_dad_tree_get_selected(attr);
	if (r != NULL)
		cursor_path = rnd_strdup(r->cell[0]);

	/* remove existing items */
	rnd_dad_tree_clear(tree);

	/* add all items recursively */
	cell[1] = NULL;
	for(n = 0; n < a->arr.used; n++) {
		cell[0] = rnd_strdup(a->arr.array[n]);
		r = rnd_dad_tree_append(attr, NULL, cell);
		r->user_data2.lng = n;
	}

	/* 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->wval_tree, &hv);
		free(cursor_path);
	}
}

static void attr2dlg_(attrdlg_ctx_t *ctx, int new_key)
{
	int is_arr = 0, is_str = 0, assistable = 0, refd = 0;
	rnd_hid_attribute_t *attr = &ctx->dlg[ctx->wattrs];
/*	rnd_hid_tree_t *tree = attr->wdata;*/
	rnd_hid_row_t *r = rnd_dad_tree_get_selected(attr);
	csch_attrib_t *a = NULL;
	rnd_hid_attr_val_t hv;

	if (r != NULL) {
		if ((r->cell[0][0] == ' ') && (ctx->obj->hdr.type == CSCH_CTYPE_GRP_REF) && (ctx->obj->data.ref.grp != NULL)) {
		/* allow selecting group ref's referencede group attributes; these will
		   just be recreated as attributes of the group ref on edit */
			a = csch_attrib_get(&ctx->obj->data.ref.grp->attr, r->cell[0]+1);
			refd = 1;
		}
		else
			a = csch_attrib_get(&ctx->obj->attr, r->cell[0]);
	}

	if (a != NULL) {
		char tmp[128];

		is_str = (a->val != NULL);
		is_arr = (a->val == NULL);

		hv.str = a->key;
		rnd_gui->attr_dlg_set_value(ctx->dlg_hid_ctx, ctx->wkey, &hv);

		hv.lng = a->prio;
		rnd_gui->attr_dlg_set_value(ctx->dlg_hid_ctx, ctx->wprio, &hv);

		sprintf(tmp, "%d", a->prio);
		hv.str = tmp;
		rnd_gui->attr_dlg_set_value(ctx->dlg_hid_ctx, ctx->wprio2, &hv);

		if (is_str) {
			hv.str = a->val;
			rnd_gui->attr_dlg_set_value(ctx->dlg_hid_ctx, ctx->wval_str, &hv);
		}
		else
			attr2dlg_arr(ctx, a);

		assistable = sch_rnd_attr_quick_editable(ctx->sheet, &ctx->obj->hdr, a->key);
	}

	if (!new_key)
		rnd_gui->attr_dlg_widget_hide(ctx->dlg_hid_ctx, ctx->wstr_sect, !is_str);

	rnd_gui->attr_dlg_widget_hide(ctx->dlg_hid_ctx, ctx->warr_sect, !is_arr);
	rnd_gui->attr_dlg_widget_state(ctx->dlg_hid_ctx, ctx->wassist, assistable);

	rnd_gui->attr_dlg_widget_state(ctx->dlg_hid_ctx, ctx->wconv_arr, !refd);
	rnd_gui->attr_dlg_widget_state(ctx->dlg_hid_ctx, ctx->wconv_str, !refd);
	rnd_gui->attr_dlg_widget_hide(ctx->dlg_hid_ctx, ctx->wprio, refd);
	rnd_gui->attr_dlg_widget_hide(ctx->dlg_hid_ctx, ctx->wprio2, !refd);

}

static void attr2dlg(attrdlg_ctx_t *ctx)
{
	attr2dlg_(ctx, 0);
}

static void sheet2dlg_concrete_attrs(attrdlg_ctx_t *ctx, csch_attribs_t *attribs, const char *banner)
{
	rnd_hid_attribute_t *attr = &ctx->dlg[ctx->wattrs];
	char *cell[4];
	long n;
	vtp0_t tmp = {0};

	csch_attrib_sort(&tmp, attribs);

	if ((tmp.used > 0) && (banner != NULL)) {
		cell[1] = NULL;
		cell[0] = rnd_strdup("");
		rnd_dad_tree_append(attr, NULL, cell);
		cell[0] = rnd_strdup(banner);
		rnd_dad_tree_append(attr, NULL, cell);
	}

	for(n = 0; n < tmp.used; n++) {
		csch_attrib_t *a = tmp.array[n];
		if (banner != NULL)
			cell[0] = rnd_strdup_printf(" %s", a->key);
		else
			cell[0] = rnd_strdup(a->key);
		cell[1] = known_attr_render_cell(a);
		if (a->val != NULL)
			cell[2] = rnd_strdup(a->val);
		else
			cell[2] = rnd_strdup("<array>");
		cell[3] = NULL;
		rnd_dad_tree_append(attr, NULL, cell);
	}


	vtp0_uninit(&tmp);
}

static void sheet2dlg_concrete(attrdlg_ctx_t *ctx, const char *new_cursor_path, int new_key)
{
	rnd_hid_attr_val_t hv;
	rnd_hid_attribute_t *attr = &ctx->dlg[ctx->wattrs];
	rnd_hid_tree_t *tree = attr->wdata;
	rnd_hid_row_t *r;
	char *cursor_path = NULL;

	/* remember cursor */
	if (new_cursor_path == NULL) {
		r = rnd_dad_tree_get_selected(attr);
		if (r != NULL)
			cursor_path = rnd_strdup(r->cell[0]);
	}

	/* remove existing items */
	rnd_dad_tree_clear(tree);

	/* add all items recursively; first immediate attributes of the object */
	sheet2dlg_concrete_attrs(ctx, &ctx->obj->attr, NULL);

	/* in case of group ref, also display, read-only, all attributes of the
	   referenced group */
	if (ctx->obj->hdr.type == CSCH_CTYPE_GRP_REF) {
		if (ctx->obj->data.ref.grp == NULL)
			csch_cgrp_ref_text2ptr(ctx->obj->hdr.sheet, ctx->obj);
		if (ctx->obj->data.ref.grp != NULL)
			sheet2dlg_concrete_attrs(ctx, &ctx->obj->data.ref.grp->attr, "**Referenced grp**");
	}


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

	attr2dlg_(ctx, new_key);
}

static void sheet2dlg_cursor(attrdlg_ctx_t *ctx, const char *new_cursor_path, int new_key)
{
	int hier_idx = 0; TODO("<- hierarchic: need a widget for this");

	sheet2dlg_concrete(ctx, new_cursor_path, new_key);
	aattr_dlg_sheet2dlg_abstract(&ctx->right, csch_cgrp_get_abstract(ctx->sheet, (csch_cgrp_t *)ctx->obj, hier_idx));
}

static void sheet2dlg(attrdlg_ctx_t *ctx)
{
	sheet2dlg_cursor(ctx, NULL, 0);
}


static void attr_select(rnd_hid_attribute_t *attrib, void *hid_ctx, rnd_hid_row_t *row)
{
	rnd_hid_tree_t *tree = attrib->wdata;
	attrdlg_ctx_t *ctx = tree->user_ctx;
	rnd_hid_row_t *r = rnd_dad_tree_get_selected(attrib);

	ctx->last_attr_key = NULL;
	if (r != NULL) {
		csch_attrib_t *a = htsp_get(&ctx->obj->attr, r->cell[0]);
		if (a != NULL)
			ctx->last_attr_key = a->key;
	}


	attr2dlg(ctx);
}

RND_INLINE csch_attrib_t *tree_get_current_attr(attrdlg_ctx_t *ctx)
{
	rnd_hid_attribute_t *attr = &ctx->dlg[ctx->wattrs];
	rnd_hid_row_t *r = rnd_dad_tree_get_selected(attr);

	if (r == NULL)
		return NULL;

	return csch_attrib_get(&ctx->obj->attr, r->cell[0]);
}

static csch_source_arg_t *attrdlg_src(void)
{
	return csch_attrib_src_c(NULL, 0, 0, "attr_dlg user input");
}

static void attr_val_set_meta_and_str(attrdlg_ctx_t *ctx, csch_attrib_t *a, int set_str)
{
	int prio, chg = 0;
	const char *key;
	const char *val;

	if (a == NULL)
		return; /* shouldn't happen: no attribute selected */

	prio = ctx->dlg[ctx->wprio].val.lng;
	key = ctx->dlg[ctx->wkey].val.str;
	val = ctx->dlg[ctx->wval_str].val.str;

	if ((key == NULL) || (*key == '\0'))
		return; /* do not allow empty key */

	if (val == NULL)
		val = ""; /* empty is an empty string; NULL would be an array */

	if (strcmp(key, a->key) != 0) {
		/* key change: rename attrib */
		ctx->refresh_lock++;
		csch_attr_modify_rename(ctx->sheet, ctx->obj, a, key, attrdlg_src(), 1);
		ctx->refresh_lock--;
		sheet2dlg_cursor(ctx, key, 0);
		return;
	}

	if (prio != a->prio)
		chg = 1;

	if ((set_str) && (strcmp(val, a->val) != 0))
		chg = 1;

	if (!chg)
		return;

	ctx->refresh_lock++;
	if (set_str)
		csch_attr_modify_str(ctx->sheet, ctx->obj, prio, key, val, attrdlg_src(), 1);
	else
		csch_attr_modify_prio(ctx->sheet, ctx->obj, a, prio, attrdlg_src(), 1);
	ctx->refresh_lock--;
	sheet2dlg(ctx);
}

static void attr_val_set_meta_cb(void *hid_ctx, void *caller_data, rnd_hid_attribute_t *attr)
{
	attrdlg_ctx_t *ctx = caller_data;
	csch_attrib_t *a = tree_get_current_attr(ctx);
	attr_val_set_meta_and_str(ctx, a, 0);
}

static void attr_strval_set_cb(void *hid_ctx, void *caller_data, rnd_hid_attribute_t *attr)
{
	attrdlg_ctx_t *ctx = caller_data;
	csch_attrib_t *a = tree_get_current_attr(ctx);

	if (a == NULL) { /* special case: new target key */
		int prio;
		const char *key, *val;

		prio = ctx->dlg[ctx->wprio].val.lng;
		key = ctx->dlg[ctx->wkey].val.str;
		val = ctx->dlg[ctx->wval_str].val.str;

		if (val == NULL)
			val = ""; /* NULL means array, empty is done using an empty string */

		ctx->refresh_lock++;
		csch_attr_modify_str(ctx->sheet, ctx->obj, prio, key, val, attrdlg_src(), 1);
		ctx->refresh_lock--;
		sheet2dlg_cursor(ctx, key, 0);
	}
	else
		attr_val_set_meta_and_str(ctx, a, 1);
}

/*** Multiline string edit dialog ***/
typedef struct {
	RND_DAD_DECL_NOINIT(dlg)
	int wtxt;
	char *new_val;
} mledit_t;

static void mledit_close_cb(void *caller_data, rnd_hid_attr_ev_t ev)
{
	mledit_t *ctx = caller_data;
	rnd_hid_attribute_t *atxt = &ctx->dlg[ctx->wtxt];
	rnd_hid_text_t *txt = atxt->wdata;

	/* remember the string accepted */
	ctx->new_val = txt->hid_get_text(atxt, ctx->dlg_hid_ctx);
}

static void attr_strval_mledit_cb(void *hid_ctx, void *caller_data, rnd_hid_attribute_t *attr)
{
	mledit_t ctx = {0};
	attrdlg_ctx_t *pctx = caller_data;
	const char *val = pctx->dlg[pctx->wval_str].val.str;

	rnd_hid_attr_val_t hv;
	rnd_hid_dad_buttons_t clbtn[] = {{"Cancel", 0}, {"OK", 1}, {NULL, 0}};


	RND_DAD_BEGIN_VBOX(ctx.dlg);
		RND_DAD_COMPFLAG(ctx.dlg, RND_HATF_EXPFILL);
		RND_DAD_TEXT(ctx.dlg, NULL);
			RND_DAD_COMPFLAG(ctx.dlg, RND_HATF_EXPFILL);
			ctx.wtxt = RND_DAD_CURRENT(ctx.dlg);
		RND_DAD_BUTTON_CLOSES(ctx.dlg, clbtn);
	RND_DAD_END(ctx.dlg);

	RND_DAD_DEFSIZE(ctx.dlg, 400, 200);
	RND_DAD_NEW("AttributeMultiline", ctx.dlg, "Atribute dialog: multiline text edit", &ctx, 1, mledit_close_cb);

	hv.str = (val == NULL ? "" : val);
	rnd_gui->attr_dlg_set_value(ctx.dlg_hid_ctx, ctx.wtxt, &hv);

	if (RND_DAD_RUN(ctx.dlg) == 1) {
		hv.str = ctx.new_val;
		rnd_gui->attr_dlg_set_value(pctx->dlg_hid_ctx, pctx->wval_str, &hv);
		attr_strval_set_cb(hid_ctx, caller_data, attr); /* get the str value set */
	}

	free(ctx.new_val);
	RND_DAD_FREE(ctx.dlg);
}


/*** array ***/
static csch_attrib_t *tree_get_current_attr_arr(attrdlg_ctx_t *ctx, long *idx, const char **key)
{
	rnd_hid_attribute_t *aattr = &ctx->dlg[ctx->wattrs];
	rnd_hid_row_t *r = rnd_dad_tree_get_selected(aattr);
	rnd_hid_attribute_t *tattr = &ctx->dlg[ctx->wval_tree];
	rnd_hid_row_t *ri = rnd_dad_tree_get_selected(tattr);

	*key = ctx->dlg[ctx->wkey].val.str;

	if ((r == NULL) || (ri == NULL))
		return NULL; /* no row selected */

	*idx = ri->user_data2.lng;
	return csch_attrib_get(&ctx->obj->attr, r->cell[0]);
}

static void attr_arrval_set(attrdlg_ctx_t *ctx, csch_attrib_t *a, const char *key, long idx, int ins_before)
{
	char *newval;

	newval = rnd_hid_prompt_for(&ctx->sheet->hidlib, "Edit attribute array entry:", (ins_before ? NULL : a->arr.array[idx]), "Attribute array value edit");
	if (newval == NULL) /* cancel - not yet implemented */
		return;

	ctx->refresh_lock++;
	if (ins_before)
		csch_attr_arr_modify_ins_before(ctx->sheet, ctx->obj, key, idx, newval, 1);
	else
		csch_attr_arr_modify_str(ctx->sheet, ctx->obj, key, idx, newval, 1);
	ctx->refresh_lock--;

	attr2dlg(ctx);

	free(newval);
}

static void attr_arrval_set_cb(void *hid_ctx, void *caller_data, rnd_hid_attribute_t *attr)
{
	attrdlg_ctx_t *ctx = caller_data;
	csch_attrib_t *a;
	const char *key;
	long idx;

	a = tree_get_current_attr_arr(ctx, &idx, &key);
	if (a == NULL)
		return; /* not found */

	attr_arrval_set(ctx, a, key, idx, 0);
}

static void attr_arrval_ins_before_cb(void *hid_ctx, void *caller_data, rnd_hid_attribute_t *attr)
{
	attrdlg_ctx_t *ctx = caller_data;
	csch_attrib_t *a;
	const char *key;
	long idx;

	a = tree_get_current_attr_arr(ctx, &idx, &key);
	if (a == NULL)
		idx = 0; /* happens when nothing is selected - insert at the front */

	attr_arrval_set(ctx, a, key, idx, 1);
}

static void attr_arrval_ins_after_cb(void *hid_ctx, void *caller_data, rnd_hid_attribute_t *attr)
{
	attrdlg_ctx_t *ctx = caller_data;
	csch_attrib_t *a;
	const char *key;
	long idx;

	a = tree_get_current_attr_arr(ctx, &idx, &key);
	if (a == NULL) {
		/* happens when nothing is selected - insert at the end */
		rnd_hid_attribute_t *aattr = &ctx->dlg[ctx->wattrs];
		rnd_hid_row_t *r = rnd_dad_tree_get_selected(aattr);
		a = csch_attrib_get(&ctx->obj->attr, r->cell[0]);
		idx = a->arr.used-1;
	}

	attr_arrval_set(ctx, a, key, idx+1, 1);
}

static void attr_arrval_del_cb(void *hid_ctx, void *caller_data, rnd_hid_attribute_t *attr)
{
	attrdlg_ctx_t *ctx = caller_data;
	csch_attrib_t *a;
	const char *key;
	long idx;

	a = tree_get_current_attr_arr(ctx, &idx, &key);
	if (a == NULL)
		return; /* not found */

	ctx->refresh_lock++;
	csch_attr_arr_modify_del(ctx->sheet, ctx->obj, key, idx, 1);
	ctx->refresh_lock--;
	sheet2dlg(ctx);
}


static void attr_arrval_move(attrdlg_ctx_t *ctx, long delta)
{
	csch_attrib_t *a;
	const char *key;
	long idx;

	a = tree_get_current_attr_arr(ctx, &idx, &key);
	if (a == NULL)
		return; /* not found */

	ctx->refresh_lock++;
	csch_attr_arr_modify_move(ctx->sheet, ctx->obj, key, idx, delta, 1);
	ctx->refresh_lock--;

	attr2dlg(ctx);
}

static void attr_arrval_move_up_cb(void *hid_ctx, void *caller_data, rnd_hid_attribute_t *attr)
{
	attr_arrval_move(caller_data, -1);
}

static void attr_arrval_move_down_cb(void *hid_ctx, void *caller_data, rnd_hid_attribute_t *attr)
{
	attr_arrval_move(caller_data, +1);
}

static void attr_conv_to(attrdlg_ctx_t *ctx, int toarr)
{
	rnd_hid_attribute_t *attr = &ctx->dlg[ctx->wattrs];
	rnd_hid_row_t *r = rnd_dad_tree_get_selected(attr);
	
	if (r == NULL)
		return;


	ctx->refresh_lock++;
	if (toarr)
		csch_attr_modify_conv_to_arr(ctx->sheet, ctx->obj, r->cell[0], 1);
	else
		csch_attr_modify_conv_to_str(ctx->sheet, ctx->obj, r->cell[0], 1);
	ctx->refresh_lock--;

	sheet2dlg(ctx);
}

static void attr_conv_to_arr_cb(void *hid_ctx, void *caller_data, rnd_hid_attribute_t *attr)
{
	attr_conv_to(caller_data, 1);
}

static void attr_conv_to_str_cb(void *hid_ctx, void *caller_data, rnd_hid_attribute_t *attr)
{
	attr_conv_to(caller_data, 0);
}

static void attr_new_cb(void *hid_ctx, void *caller_data, rnd_hid_attribute_t *attr)
{
	attrdlg_ctx_t *ctx = caller_data;
	char *key, *val;
	
	
	key = rnd_hid_prompt_for(&ctx->sheet->hidlib, "Key for the new attribute", NULL, "Create new attribute");
	if ((key == NULL) || (*key == '\0'))
		return;

	val = rnd_hid_prompt_for(&ctx->sheet->hidlib, "Value for the new attribute", NULL, "Create new attribute: value");
	if (val == NULL)
		return;


	if (!htsp_has(&ctx->obj->attr, key) || (*val != '\0')) {
		ctx->refresh_lock++;
		csch_attr_modify_str(ctx->sheet, ctx->obj, CSCH_ATP_USER_DEFAULT, key, val, attrdlg_src(), 1);
		ctx->refresh_lock--;
	}

	sheet2dlg_cursor(ctx, key, 0);
	free(key);
}

static void attr_floater_cb(void *hid_ctx, void *caller_data, rnd_hid_attribute_t *attr)
{
	attrdlg_ctx_t *ctx = caller_data;
	rnd_hid_attribute_t *tattr = &ctx->dlg[ctx->wattrs];
	rnd_hid_row_t *r = rnd_dad_tree_get_selected(tattr);
	csch_cgrp_t *grp = (csch_cgrp_t *)ctx->obj;
	const char *penname;

	if (r == NULL) {
		rnd_message(RND_MSG_ERROR, "Select an attribute first!\n");
		return;
	}

	penname = (grp->role == CSCH_ROLE_WIRE_NET ? "wire" : "sym-secondary");
	csch_auto_attr_place(ctx->sheet, grp, r->cell[0], penname, NULL, ctx->ch_xy);
}

static void attr_afloater_cb(void *hid_ctx, void *caller_data, rnd_hid_attribute_t *attr)
{
	attrdlg_ctx_t *ctx = caller_data;
	rnd_hid_attribute_t *tattr = &ctx->dlg[ctx->right.waattrs];
	rnd_hid_row_t *r = rnd_dad_tree_get_selected(tattr);
	csch_cgrp_t *grp = (csch_cgrp_t *)ctx->obj;
	const char *penname;
	char *templ;

	penname = (grp->role == CSCH_ROLE_WIRE_NET ? "wire" : "sym-secondary");

	if (grp->hdr.type == CSCH_CTYPE_GRP) {
		if (r == NULL) {
			rnd_message(RND_MSG_ERROR, "First select an abstract model attribe!\n");
			return;
		}
		templ = rnd_strdup_printf("%%../a.%s%%", r->cell[0]); /* use the abstract attribute */
		csch_auto_attr_place(ctx->sheet, grp, r->cell[0], penname, templ, ctx->ch_xy);
		free(templ);
	}
	else
		rnd_message(RND_MSG_ERROR, "Can not create floater on group ref object.\nIf this is a symbol from a local lib: can not modify instance (by creating a text object for the floater); embed it first to have a non-local-lib copy that can be modified.\n");
}



static void attr_del_cb(void *hid_ctx, void *caller_data, rnd_hid_attribute_t *attr)
{
	attrdlg_ctx_t *ctx = caller_data;
	rnd_hid_attribute_t *tattr = &ctx->dlg[ctx->wattrs];
	rnd_hid_row_t *r = rnd_dad_tree_get_selected(tattr);

	if (r == NULL)
		return;

	ctx->refresh_lock++;
	csch_attr_modify_del(ctx->sheet, ctx->obj, r->cell[0], 1);
	ctx->refresh_lock--;
	sheet2dlg(ctx);
}

static void attr_assist_cb(void *hid_ctx, void *caller_data, rnd_hid_attribute_t *attr)
{
	attrdlg_ctx_t *ctx = caller_data;
	rnd_hid_attribute_t *tattr = &ctx->dlg[ctx->wattrs];
	rnd_hid_row_t *r = rnd_dad_tree_get_selected(tattr);
	int res;

	if (r == NULL)
		return;

	ctx->refresh_lock++;
	res = sch_rnd_attr_quick_edit(ctx->sheet, &ctx->obj->hdr, r->cell[0]);
	ctx->refresh_lock--;
	if (res > 0)
		sheet2dlg(ctx);
}


static void aattr_select(rnd_hid_attribute_t *attrib, void *hid_ctx, rnd_hid_row_t *row)
{
	rnd_hid_tree_t *tree = attrib->wdata;
	attrdlg_ctx_t *ctx = tree->user_ctx;

	int hier_idx = 0; TODO("hierarchic: <- need a widget for this")

	aattr_dlg_ahist2dlg(&ctx->right, csch_cgrp_get_abstract(ctx->sheet, (csch_cgrp_t *)ctx->obj, hier_idx)); /* update history */
}


static void spring(attrdlg_ctx_t *ctx)
{
	RND_DAD_BEGIN_HBOX(ctx->dlg);
		RND_DAD_COMPFLAG(ctx->dlg, RND_HATF_EXPFILL);
	RND_DAD_END(ctx->dlg);
}

static attrdlg_ctx_t *attr_dlg_find(csch_sheet_t *sheet, csch_cgrp_t *obj)
{
	attrdlg_ctx_t *n;
	for(n = gdl_first(&attrdlgs); n != NULL; n = gdl_next(&attrdlgs, n)) {
		if ((n->sheet == sheet) && (n->obj == obj))
			return n;
	}
	return NULL;
}

static void aattr_sources_cb(void *hid_ctx, void *caller_data, rnd_hid_attribute_t *attr)
{
	attrdlg_ctx_t *ctx = caller_data;
	aattr_sources(&ctx->right);
}

static void aattr_attr_src_cb(void *hid_ctx, void *caller_data, rnd_hid_attribute_t *attr)
{
	attrdlg_ctx_t *ctx = caller_data;
	aattr_attr_src(&ctx->right);
}

/* If pick is true, return the attrib key picked by the user (or NULL) */
static const char *attr_dlg(csch_sheet_t *sheet, csch_cgrp_t *obj, const char *target_key, int pick, int has_coords)
{
	attrdlg_ctx_t *ctx;
	rnd_hid_dad_buttons_t clbtn[] = {{"Close", 0}, {NULL, 0}};
	rnd_hid_dad_buttons_t clbtn_pick[] = {{"Cancel", -1}, {"Use selected", 1}, {NULL, 0}};
	const char *hdr[] = {"key", " ", "value", NULL};
	const char *oname, *orole, *req;
	int modal = pick, new_key = 0;

	if (!modal) {
		ctx = attr_dlg_find(sheet, obj);
		if (ctx != NULL) {
			TODO("raise?");
			return NULL;
		}
	}

	oname = csch_attrib_get_str(&obj->attr, "name");
	orole = csch_attrib_get_str(&obj->attr, "role");
	req = (pick ? "Pick an attribute in" : "Attributes of");

	ctx = calloc(sizeof(attrdlg_ctx_t), 1);
	ctx->sheet = sheet;
	ctx->obj = obj;
	ctx->modal = modal;
	ctx->name = rnd_strdup_printf("%s %s %s", req, (orole == NULL ? "" : orole), (oname == NULL ? "" : oname));
	gdl_append(&attrdlgs, ctx, link);

	if (has_coords) {
		ctx->ch_xy_[0] = P2C(sch_rnd_crosshair_x);
		ctx->ch_xy_[1] = P2C(sch_rnd_crosshair_y);
		ctx->ch_xy = ctx->ch_xy_;
	}


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

			RND_DAD_BEGIN_VBOX(ctx->dlg); /* left */
				RND_DAD_COMPFLAG(ctx->dlg, RND_HATF_EXPFILL);
				RND_DAD_LABEL(ctx->dlg, "Concrete:");
				RND_DAD_BEGIN_VPANE(ctx->dlg, "left_top-bottom");

					RND_DAD_TREE(ctx->dlg, 3, 0, hdr);
						RND_DAD_COMPFLAG(ctx->dlg, RND_HATF_EXPFILL | RND_HATF_SCROLL);
						ctx->wattrs = RND_DAD_CURRENT(ctx->dlg);
						RND_DAD_TREE_SET_CB(ctx->dlg, selected_cb, attr_select);
						RND_DAD_TREE_SET_CB(ctx->dlg, ctx, ctx);

					RND_DAD_BEGIN_VBOX(ctx->dlg); /* bottom left: value edit */
						RND_DAD_COMPFLAG(ctx->dlg, RND_HATF_EXPFILL);
						ctx->wvaledit = RND_DAD_CURRENT(ctx->dlg);

						RND_DAD_BEGIN_HBOX(ctx->dlg); /* new/del */
						RND_DAD_COMPFLAG(ctx->dlg, RND_HATF_FRAME);
							RND_DAD_BUTTON(ctx->dlg, "Assisted edit");
								ctx->wassist = RND_DAD_CURRENT(ctx->dlg);
								RND_DAD_CHANGE_CB(ctx->dlg, attr_assist_cb);
								RND_DAD_HELP(ctx->dlg, "Invoke attribute-specitic quick edit if available");
							spring(ctx);
							RND_DAD_BUTTON(ctx->dlg, "Floater");
								ctx->wfloater = RND_DAD_CURRENT(ctx->dlg);
								RND_DAD_CHANGE_CB(ctx->dlg, attr_floater_cb);
								RND_DAD_HELP(ctx->dlg, "Create a floater text that prints the value of the attribute\nfrom the concrete model (value before compilation)");
							spring(ctx);
							RND_DAD_BUTTON(ctx->dlg, "New");
								RND_DAD_CHANGE_CB(ctx->dlg, attr_new_cb);
							RND_DAD_BUTTON(ctx->dlg, "Del");
								RND_DAD_CHANGE_CB(ctx->dlg, attr_del_cb);
						RND_DAD_END(ctx->dlg);

						RND_DAD_BEGIN_HBOX(ctx->dlg);
							RND_DAD_LABEL(ctx->dlg, "Key:");
							RND_DAD_STRING(ctx->dlg);
								RND_DAD_WIDTH_CHR(ctx->dlg, 32);
								RND_DAD_ENTER_CB(ctx->dlg, attr_val_set_meta_cb);
								ctx->wkey = RND_DAD_CURRENT(ctx->dlg);
						RND_DAD_END(ctx->dlg);
						RND_DAD_BEGIN_HBOX(ctx->dlg);
							RND_DAD_LABEL(ctx->dlg, "Priority:");
							RND_DAD_INTEGER(ctx->dlg);
								RND_DAD_WIDTH_CHR(ctx->dlg, 5);
								RND_DAD_ENTER_CB(ctx->dlg, attr_val_set_meta_cb);
								ctx->wprio = RND_DAD_CURRENT(ctx->dlg);
							RND_DAD_LABEL(ctx->dlg, "");
								ctx->wprio2 = RND_DAD_CURRENT(ctx->dlg);
						RND_DAD_END(ctx->dlg);

						/* string value */
						RND_DAD_BEGIN_VBOX(ctx->dlg);
							ctx->wstr_sect = RND_DAD_CURRENT(ctx->dlg);
							RND_DAD_BEGIN_HBOX(ctx->dlg);
								RND_DAD_LABEL(ctx->dlg, "Val:");
								RND_DAD_STRING(ctx->dlg);
									if (!pick && (target_key != NULL))
										RND_DAD_COMPFLAG(ctx->dlg, RND_HATF_INIT_FOCUS);
									RND_DAD_WIDTH_CHR(ctx->dlg, 32);
									RND_DAD_ENTER_CB(ctx->dlg, attr_strval_set_cb);
									ctx->wval_str = RND_DAD_CURRENT(ctx->dlg);
							RND_DAD_END(ctx->dlg);
							RND_DAD_BEGIN_HBOX(ctx->dlg);
								RND_DAD_BUTTON(ctx->dlg, "Convert to array");
									RND_DAD_CHANGE_CB(ctx->dlg, attr_conv_to_arr_cb);
									ctx->wconv_arr = RND_DAD_CURRENT(ctx->dlg);
								spring(ctx);
								RND_DAD_BUTTON(ctx->dlg, "Edit multiline");
									RND_DAD_CHANGE_CB(ctx->dlg, attr_strval_mledit_cb);
								RND_DAD_BUTTON(ctx->dlg, "Set");
									RND_DAD_CHANGE_CB(ctx->dlg, attr_strval_set_cb);
							RND_DAD_END(ctx->dlg);
						RND_DAD_END(ctx->dlg);

						/* array value */
						RND_DAD_BEGIN_VBOX(ctx->dlg);
							RND_DAD_COMPFLAG(ctx->dlg, RND_HATF_EXPFILL);
							ctx->warr_sect = RND_DAD_CURRENT(ctx->dlg);
							RND_DAD_BEGIN_HBOX(ctx->dlg);
								RND_DAD_LABEL(ctx->dlg, "Val:");
								RND_DAD_BUTTON(ctx->dlg, "Convert to string");
									RND_DAD_COMPFLAG(ctx->dlg, RND_HATF_TIGHT);
									RND_DAD_CHANGE_CB(ctx->dlg, attr_conv_to_str_cb);
									ctx->wconv_str = RND_DAD_CURRENT(ctx->dlg);
								spring(ctx);
								RND_DAD_BUTTON(ctx->dlg, "Set prio/key");
									RND_DAD_CHANGE_CB(ctx->dlg, attr_val_set_meta_cb);
							RND_DAD_END(ctx->dlg);
							RND_DAD_TREE(ctx->dlg, 1, 0, NULL);
								RND_DAD_COMPFLAG(ctx->dlg, RND_HATF_EXPFILL | RND_HATF_SCROLL);
								ctx->wval_tree = RND_DAD_CURRENT(ctx->dlg);
							RND_DAD_BEGIN_HBOX(ctx->dlg); /* array buttons 1 */
								RND_DAD_BUTTON(ctx->dlg, "Edit...");
									RND_DAD_CHANGE_CB(ctx->dlg, attr_arrval_set_cb);
								RND_DAD_BUTTON(ctx->dlg, "Move up");
									RND_DAD_CHANGE_CB(ctx->dlg, attr_arrval_move_up_cb);
								RND_DAD_BUTTON(ctx->dlg, "Move down");
									RND_DAD_CHANGE_CB(ctx->dlg, attr_arrval_move_down_cb);
							RND_DAD_END(ctx->dlg);
							RND_DAD_BEGIN_HBOX(ctx->dlg); /* array buttons 2 */
								RND_DAD_BUTTON(ctx->dlg, "Delete");
									RND_DAD_CHANGE_CB(ctx->dlg, attr_arrval_del_cb);
								RND_DAD_BUTTON(ctx->dlg, "Ins before");
									RND_DAD_CHANGE_CB(ctx->dlg, attr_arrval_ins_before_cb);
								RND_DAD_BUTTON(ctx->dlg, "Ins after");
									RND_DAD_CHANGE_CB(ctx->dlg, attr_arrval_ins_after_cb);
							RND_DAD_END(ctx->dlg);

						RND_DAD_END(ctx->dlg); /* bottom left */
					RND_DAD_END(ctx->dlg); /* left vpane */
				RND_DAD_END(ctx->dlg); /* left side */

			RND_DAD_END(ctx->dlg);

			RND_DAD_BEGIN_VBOX(ctx->dlg); /* top right */
				RND_DAD_COMPFLAG(ctx->dlg, RND_HATF_EXPFILL);
				aattr_dlg_create(&ctx->right, ctx->dlg, (csch_project_t *)sheet->hidlib.project, attr_afloater_cb);
			RND_DAD_END(ctx->dlg);
		RND_DAD_END(ctx->dlg);

		RND_DAD_BUTTON_CLOSES(ctx->dlg, pick ? clbtn_pick : clbtn);
	RND_DAD_END(ctx->dlg);

	RND_DAD_DEFSIZE(ctx->dlg, 400, 300);
	RND_DAD_NEW("AttributeDialog", ctx->dlg, ctx->name, ctx, modal, attrdlg_close_cb); /* type=local */

	aattr_dlg_init(&ctx->right);

	if (pick) {
		rnd_gui->attr_dlg_widget_hide(ctx->dlg_hid_ctx, ctx->wvaledit, 1);
		rnd_gui->attr_dlg_widget_hide(ctx->dlg_hid_ctx, ctx->right.wright, 1);
	}

 /* grp refs shouldn't allow floaters to be created: no new object in a ref */
	if (obj->hdr.type != CSCH_CTYPE_GRP)
		rnd_gui->attr_dlg_widget_state(ctx->dlg_hid_ctx, ctx->wfloater, 0);

	if (!pick && (target_key != NULL))
		new_key = !htsp_has(&obj->attr, target_key);

	sheet2dlg_cursor(ctx, target_key, new_key);
	if (!pick && (target_key != NULL)) {
		if (new_key) { /* fill in key and prio */
			rnd_hid_attr_val_t hv;

			hv.str = target_key;
			rnd_gui->attr_dlg_set_value(ctx->dlg_hid_ctx, ctx->wkey, &hv);

			hv.lng = CSCH_ATP_USER_DEFAULT;
			rnd_gui->attr_dlg_set_value(ctx->dlg_hid_ctx, ctx->wprio, &hv);
		}
		else
			RND_DAD_STRING_SELECT_REGION(ctx->dlg_hid_ctx, ctx->wval_str, 0, 1<<30);
	}

	if (modal) {
		const char *ret = NULL;

		if (RND_DAD_RUN(ctx->dlg) == 1) {
			if (ctx->last_attr_key != NULL)
				ret = ctx->last_attr_key;
		}

		free(ctx);
		return ret;
	}
	return NULL;
}


extern csch_chdr_t *csch_obj_clicked;

RND_INLINE csch_chdr_t *resolve_obj_(csch_sheet_t *sheet, const char *actname, const char *cmd, int *has_coords)
{
	*has_coords = 0;

	if (strcmp(cmd, "last-click") == 0) {
		*has_coords = 1;
		return csch_obj_clicked;
	}
	else if (strcmp(cmd, "parent") == 0) {
		if (csch_obj_clicked != NULL) {
			*has_coords = 1;
			return &csch_obj_clicked->parent->hdr;
		}
	}
	else if (strcmp(cmd, "sheet") == 0) {
		return &sheet->direct.hdr;
	}
	else if (strncmp(cmd, "object", 6) == 0) {
		if (cmd[6] == ':') {
			csch_chdr_t *obj;
			csch_oidpath_t idp = {0};

			if (csch_oidpath_parse(&idp, cmd+7) != 0) {
				rnd_message(RND_MSG_ERROR, "%s: Failed to convert object ID: '%s'\n", actname, cmd+7);
				return NULL;
			}
			obj = csch_oidpath_resolve(sheet, &idp);
			csch_oidpath_free(&idp);
			return obj;
		}
		else {
			rnd_coord_t x, y;

			rnd_hid_get_coords("Attribute edit/pick dialog: select object", &x, &y, 0);
			*has_coords = 1;
			return sch_rnd_search_first_gui_inspect(sheet, sch_rnd_crosshair_x, sch_rnd_crosshair_y);
		}
	}
	else {
		rnd_message(RND_MSG_ERROR, "%s: invalid first arg\n", actname);
		return NULL;
	}

	rnd_message(RND_MSG_ERROR, "%s: no such object\n", actname);
	return NULL;
}

csch_chdr_t *sch_dialog_resolve_obj(csch_sheet_t *sheet, const char *actname, const char *cmd, int *has_coords)
{
	csch_chdr_t *obj = resolve_obj_(sheet, actname, cmd, has_coords);

	/* if found a floater or wire, fall back to parent silently but do not end up editing sheet attrs that way */
	if ((obj != NULL) && (!csch_obj_is_grp(obj))) {
		obj = &obj->parent->hdr;
		if (obj == &sheet->direct.hdr) {
			rnd_message(RND_MSG_ERROR, "%s: object is not a group.\n(Only groups have attributes)\n", actname);
			return NULL;
		}
	}

	if (!csch_obj_is_grp(obj)) {
		rnd_message(RND_MSG_ERROR, "%s: object is not a group.\n(Only groups have attributes)\n", actname);
		return NULL;
	}
	return obj;
}


const char csch_acts_AttributeDialog[] = "AttributeDialog([last-click|parent|sheet|object[:idpath]], [target_key])";
const char csch_acth_AttributeDialog[] = "Bring up the Attribute Editor Dialog for an object.\n";
fgw_error_t csch_act_AttributeDialog(fgw_arg_t *res, int argc, fgw_arg_t *argv)
{
	rnd_design_t *hidlib = RND_ACT_DESIGN;
	csch_sheet_t *sheet = (csch_sheet_t *)hidlib;
	const char *cmd = "object", *target_key = NULL;
	csch_chdr_t *obj = NULL;
	int has_coords;

	RND_ACT_MAY_CONVARG(1, FGW_STR, AttributeDialog, cmd = argv[1].val.str);
	RND_ACT_MAY_CONVARG(2, FGW_STR, AttributeDialog, target_key = argv[2].val.str);

	obj = sch_dialog_resolve_obj(sheet, "AttributeDialog", cmd, &has_coords);
	if (obj == NULL) {
		RND_ACT_IRES(-1);
		return 0;
	}

	if (!csch_obj_is_grp(obj)) {
		rnd_message(RND_MSG_ERROR, "AttributeDialog(): object is not a group\n");
		RND_ACT_IRES(-1);
		return 0;
	}

	attr_dlg(sheet, (csch_cgrp_t *)obj, target_key, 0, has_coords);

	RND_ACT_IRES(0);
	return 0;
}

const char csch_acts_AttributePick[] = "AttributePick([last-click|parent|object[:idpath]], [target_key])";
const char csch_acth_AttributePick[] = "Bring up the Attribute Pick Dialog and ask the user to pick an attribute of an object.\n";
fgw_error_t csch_act_AttributePick(fgw_arg_t *res, int argc, fgw_arg_t *argv)
{
	rnd_design_t *hidlib = RND_ACT_DESIGN;
	csch_sheet_t *sheet = (csch_sheet_t *)hidlib;
	const char *cmd = "object", *target_key = NULL;
	csch_chdr_t *obj = NULL;
	const char *key;
	int has_coords;

	RND_ACT_MAY_CONVARG(1, FGW_STR, AttributePick, cmd = argv[1].val.str);

	obj = sch_dialog_resolve_obj(sheet, "AttributePick", cmd, &has_coords);
	if ((obj == NULL) || (!csch_obj_is_grp(obj))) {
		res->type = FGW_PTR;
		res->val.ptr_void = NULL;
		return 0;
	}

	key = attr_dlg(sheet, (csch_cgrp_t *)obj, target_key, 1, has_coords);

	res->type = FGW_STR;
	res->val.cstr = key;
	return 0;
}


void csch_dlg_attr_preunload(csch_sheet_t *sheet)
{
	attrdlg_ctx_t *n, *next;
	rnd_dad_retovr_t retovr = {0};

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


void csch_dlg_attr_edit(csch_sheet_t *sheet)
{
	attrdlg_ctx_t *n, *next;
	rnd_dad_retovr_t retovr = {0};

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

void csch_dlg_attr_compiled(csch_project_t *prj)
{
	attrdlg_ctx_t *n, *next;

	for(n = gdl_first(&attrdlgs); n != NULL; n = next) {
		next = gdl_next(&attrdlgs, n);
		if ((csch_project_t *)n->sheet->hidlib.project == prj)
			sheet2dlg(n);
	}
}


void csch_dlg_attr_obj_attr_edit(csch_sheet_t *sheet, csch_cgrp_t *obj)
{
	attrdlg_ctx_t *n;

	for(n = gdl_first(&attrdlgs); n != NULL; n = gdl_next(&attrdlgs, n))
		if (!n->refresh_lock && (n->sheet == sheet) && (n->obj == obj))
			sheet2dlg(n);
}

