/*
 *                            COPYRIGHT
 *
 *  sch-rnd - modular/flexible schematics editor - GUI - tree view dialog
 *  Copyright (C) 2022 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
 */

/* object tree dialog */

#include <libcschem/config.h>

#include <genht/htpp.h>
#include <genht/htpi.h>
#include <genht/hash.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 <libcschem/concrete.h>
#include <libcschem/project.h>
#include <libcschem/actions_csch.h>
#include <libcschem/cnc_any_obj.h>
#include <libcschem/cnc_conn.h>
#include <libcschem/operation.h>
#include <libcschem/util_wirenet.h>

#include <sch-rnd/draw.h>
#include <sch-rnd/search.h>
#include <sch-rnd/funchash_core.h>
#include <sch-rnd/crosshair.h>

typedef enum {
	OBJ_ST_NONE = 0,           /* do not include in the list */
	OBJ_ST_DIRECT,             /* directly member of the list */
	OBJ_ST_INDIRECT            /* not really a member, included only because it's a parent group of a member */
} tree_dlg_obj_state_t;

typedef struct tree_dlg_ctx_s {
	RND_DAD_DECL_NOINIT(dlg)
	csch_project_t *prj;
	htpi_t only;         /* key: (csch_chdr_t *); val: tree_dlg_obj_state_t */
	unsigned only_vis:1; /* if 1, only include objects that are in ->only */

	int wtree, wupdate, wprev, wdetails, wattredit;

	rnd_box_t prvbb;

	rnd_hidval_t timer;
	int timer_active;

	csch_chdr_t *preview_obj; /* last object drawn on preview */
	gds_t path; /* temp storage for building paths on update (alloc cache) */
} tree_dlg_ctx_t;

#define CSCH_TREE_CHG_TIMEOUT_NORMAL 3000
#define CSCH_TREE_CHG_TIMEOUT_QUICK  300

static htpp_t prj2dlg;
static vtl0_t hisave;

static void tree_dlg_close_cb(void *caller_data, rnd_hid_attr_ev_t ev)
{
	tree_dlg_ctx_t *ctx = caller_data;

	if (ctx->timer_active)
		rnd_gui->stop_timer(rnd_gui, ctx->timer);
	gds_uninit(&ctx->path);
	if (ctx->only_vis)
		htpi_uninit(&ctx->only);
	htpp_pop(&prj2dlg, ctx->prj);
	free(ctx);
}

static const char *get_subtype(csch_chdr_t *obj)
{
	csch_cgrp_t *grp = (csch_cgrp_t *)obj;

	if (obj == &obj->sheet->direct.hdr)
		return "Sheet";

	if (csch_obj_is_grp(obj)) {
		if ((grp->srole == NULL) || (*grp->srole == '\0')) {
			const char *purp = csch_attrib_get_str(&grp->attr, "purpose");
			if (purp != NULL)
				return purp;
		}
		return grp->srole;
	}

	if (obj->parent->role == CSCH_ROLE_WIRE_NET) {
		if (csch_obj_is_junction(obj))
			return "junction";
		return "wire";
	}

	return "";
}

static const char *get_name(csch_chdr_t *obj)
{
	csch_cgrp_t *grp;

	if (obj == &obj->sheet->direct.hdr)
		return obj->sheet->hidlib.loadname;

	if (!csch_obj_is_grp(obj))
		return "";

	grp = (csch_cgrp_t *)obj;
	return csch_attrib_get_str(&grp->attr, "name");
}

RND_INLINE int strcmp_safe(const char *s1, const char *s2)
{
	if ((s1 == NULL) && (s2 == NULL)) return 0;
	if ((s1 == NULL) || (s2 == NULL)) return 1;
	return strcmp(s1, s2);
}

static void tree_update_(tree_dlg_ctx_t *ctx, rnd_hid_attribute_t *attr, rnd_hid_row_t *parent, csch_chdr_t *obj)
{
	rnd_hid_row_t *row;
	rnd_hid_tree_t *tree = attr->wdata;
	int save, len, idi, changed = 0, state = 1;
	char *cols[5];
	const char *subtype, *name;

	if (obj->oid < 0)
		return;

	if (ctx->only_vis) {
		state = htpi_get(&ctx->only, obj);
		if (state == OBJ_ST_NONE)
			return;
	}

	/* create path */
	if (ctx->path.alloced < ctx->path.used + 64) {
		long save = ctx->path.used;
		gds_enlarge(&ctx->path, ctx->path.used + 128);
		ctx->path.used = save;
	}

	save = ctx->path.used;
	if (ctx->path.used != 0)
		gds_append(&ctx->path, '/');

	idi = ctx->path.used;
	if (obj == &obj->sheet->direct.hdr)
		len = sprintf(ctx->path.array + ctx->path.used, "{%ld} %ld", (long)obj->sheet->uid, (long)obj->oid);
	else
		len = sprintf(ctx->path.array + ctx->path.used, "%ld", (long)obj->oid);
	ctx->path.used += len;

	row = htsp_get(&tree->paths, ctx->path.array);
	changed = (row != NULL) && (row->user_data != obj);

	subtype = get_subtype(obj);
	name = get_name(obj);

	/* detect content change */
	if ((row != NULL) && (!changed))
		changed = (strcmp_safe(subtype, row->cell[2]) != 0) || (strcmp_safe(name, row->cell[3]) != 0);

	if ((row == NULL) || changed) {
		cols[0] = rnd_strdup(ctx->path.array + idi);
		if (state == OBJ_ST_INDIRECT)
			cols[1] = rnd_strdup_printf("(%s)", csch_ctype_name(obj->type));
		else
			cols[1] = rnd_strdup(csch_ctype_name(obj->type));
		cols[2] = rnd_strdup(subtype == NULL ? "" : subtype);
		cols[3] = rnd_strdup(name == NULL ? "" : name);
		cols[4] = NULL;
	}

	if (row == NULL) {
		row = rnd_dad_tree_append_under(attr, parent, cols);
	}
	else if (changed) {
		rnd_dad_tree_modify_cell(attr, row, 1, cols[1]);
		rnd_dad_tree_modify_cell(attr, row, 2, cols[2]);
		rnd_dad_tree_modify_cell(attr, row, 3, cols[3]);
		free(cols[0]);
	}
	row->user_data = obj;
	row->user_data2.lng++;

	/* recurse groups */
	if (csch_obj_is_grp(obj)) {
		csch_cgrp_t *g = (csch_cgrp_t *)obj;
		htip_entry_t *e;

		for(e = htip_first(&g->id2obj); e != NULL; e = htip_next(&g->id2obj, e))
			tree_update_(ctx, attr, row, e->value);
	}

	ctx->path.used = save;
}

static void tree_update_details(tree_dlg_ctx_t *ctx, rnd_hid_row_t *row)
{
	gds_t tmp = {0};
	char *text = NULL;
	rnd_hid_attr_val_t hv;
	int can_attr_edit = 0;

	if ((row != NULL) && (row->user_data != NULL)) {
		csch_chdr_t *obj = row->user_data;

		if (csch_obj_is_grp(obj)) {
			csch_cgrp_t *grp = (csch_cgrp_t *)obj;
			const char *purp = csch_attrib_get_str(&grp->attr, "purpose");
			const char *name = csch_attrib_get_str(&grp->attr, "name");

			can_attr_edit = 1;

			gds_append_str(&tmp, "Group object:\n\nrole=");
			if (grp->srole != NULL)
				gds_append_str(&tmp, grp->srole);

			gds_append_str(&tmp, "\npurpose=");
			if (purp != NULL)
				gds_append_str(&tmp, purp);

			gds_append_str(&tmp, "\nname=");
			if (name != NULL)
				gds_append_str(&tmp, name);

			rnd_append_printf(&tmp, "\nxform: r=%.2f mx=%d my=%d", grp->xform.rot, grp->xform.mirx, grp->xform.miry);


			gds_append(&tmp, '\n');
			text = tmp.array;
		}
		if (obj->type == CSCH_CTYPE_CONN) {
			long n;
			csch_conn_t *conn = (csch_conn_t *)obj;
			csch_oidpath_t oidp = {0};

			gds_append_str(&tmp, "Connection object; connects:\n");
			for(n = 0; n < conn->conn.used; n++) {
				csch_oidpath_from_obj(&oidp, conn->conn.array[n]);
				gds_append(&tmp, ' ');
				csch_oidpath_to_str_append(&tmp, &oidp);
				gds_append(&tmp, '\n');
				csch_oidpath_free(&oidp);
			}

			gds_append(&tmp, '\n');
			text = tmp.array;
		}
		else
			text = "Atomic drawing object.";
	}
	else
		text = "(no object picked)";


	hv.str = text;
	rnd_gui->attr_dlg_set_value(ctx->dlg_hid_ctx, ctx->wdetails, &hv);

	gds_uninit(&tmp);

	rnd_gui->attr_dlg_widget_state(ctx->dlg_hid_ctx, ctx->wattredit, can_attr_edit);
}

static void tree_update_prj(tree_dlg_ctx_t *ctx, rnd_hid_attribute_t *attr)
{
	long n;
	for(n = 0; n < ctx->prj->hdr.designs.used; n++) {
		csch_sheet_t *sheet = ctx->prj->hdr.designs.array[n];
		if (sheet != NULL)
			tree_update_(ctx, attr, NULL, &sheet->direct.hdr);
	}
}


static void tree_update(tree_dlg_ctx_t *ctx)
{
	rnd_hid_attribute_t *attr = &ctx->dlg[ctx->wtree];
	rnd_hid_tree_t *tree = attr->wdata;
	htsp_entry_t *e;

	/* clear user data on all existing rows */
	for(e = htsp_first(&tree->paths); e != NULL; e = htsp_next(&tree->paths, e)) {
		rnd_hid_row_t *row = e->value;
		row->user_data2.lng = 0;
	}

	ctx->path.used = 0;
	tree_update_prj(ctx, attr);

	/* remove any row not identified in update_() */
	for(e = htsp_first(&tree->paths); e != NULL; e = htsp_next(&tree->paths, e)) {
		rnd_hid_row_t *row = e->value;
		if (row->user_data2.lng == 0)
			rnd_dad_tree_remove(attr, row);
	}
	tree_update_details(ctx, rnd_dad_tree_get_selected(attr));
}

static csch_chdr_t *get_dlg_obj(tree_dlg_ctx_t *ctx, int silent)
{
	csch_chdr_t *obj;
	rnd_hid_attribute_t *attr = &ctx->dlg[ctx->wtree];
	rnd_hid_row_t *r = rnd_dad_tree_get_selected(attr);
	if (r == NULL)
		return NULL;
	
	obj = r->user_data;
	if (csch_obj_is_deleted(obj)) {
		if (!silent)
			rnd_message(RND_MSG_ERROR, "Object is inactive (deleted)\n");
		return NULL;
	}

	return obj;
}

static void tree_preview_bbox(tree_dlg_ctx_t *ctx, csch_chdr_t *obj)
{
	long dx, dy;

	if (obj != NULL) {
		if (obj->type == CSCH_CTYPE_CONN) {
			long n;
			csch_conn_t *conn = (csch_conn_t *)obj;
			ctx->prvbb.X1 = ctx->prvbb.Y1 = CSCH_COORD_MAX;
			ctx->prvbb.X2 = ctx->prvbb.Y2 = -CSCH_COORD_MAX;
			for(n = 0; n < conn->coords.used; n+=2)
				rnd_box_bump_point(&ctx->prvbb, conn->coords.array[n], conn->coords.array[n+1]);
			dx = dy = 8000;
		}
		else {
			ctx->prvbb.X1 = obj->bbox.x1; ctx->prvbb.Y1 = obj->bbox.y1;
			ctx->prvbb.X2 = obj->bbox.x2; ctx->prvbb.Y2 = obj->bbox.y2;
			/* 12.5% margin */
			dx = (ctx->prvbb.X2 - ctx->prvbb.X1) / 8;
			dy = (ctx->prvbb.Y2 - ctx->prvbb.Y1) / 8;
		}

		/* add a dx;dy margin around the box, zooming out a bit, for context */
		ctx->prvbb.X1 = C2P(ctx->prvbb.X1 - dx);
		ctx->prvbb.Y1 = C2P(ctx->prvbb.Y1 - dy);
		ctx->prvbb.X2 = C2P(ctx->prvbb.X2 + dx);
		ctx->prvbb.Y2 = C2P(ctx->prvbb.Y2 + dy);
	}
	else {
		dx = dy = 0;
		ctx->prvbb.X1 = ctx->prvbb.Y1 = ctx->prvbb.X2 = ctx->prvbb.Y2 = 0;
	}

}

static void tree_update_preview(tree_dlg_ctx_t *ctx)
{
	csch_chdr_t *obj = get_dlg_obj(ctx, 1);

	if ((obj != NULL) && (obj != ctx->preview_obj)) {
		tree_preview_bbox(ctx, obj);
		rnd_dad_preview_zoomto(&ctx->dlg[ctx->wprev], &ctx->prvbb);
	}
	else
		rnd_dad_preview_zoomto(&ctx->dlg[ctx->wprev], NULL); /* redraw only */

	ctx->preview_obj = obj;
}

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

	tree_update_preview(ctx);
	tree_update_details(ctx, row);
}

static void tree_select_cb(void *hid_ctx, void *caller_data, rnd_hid_attribute_t *attr)
{
	tree_dlg_ctx_t *ctx = caller_data;
	csch_chdr_t *obj = get_dlg_obj(ctx, 0);

	if ((obj != NULL) && !obj->selected) {
		csch_cobj_select(obj->sheet, obj);
		tree_update_preview(ctx);
	}
}

static void tree_unselect_cb(void *hid_ctx, void *caller_data, rnd_hid_attribute_t *attr)
{
	tree_dlg_ctx_t *ctx = caller_data;
	csch_chdr_t *obj = get_dlg_obj(ctx, 0);

	if ((obj != NULL) && obj->selected) {
		csch_cobj_unselect(obj->sheet, obj);
		tree_update_preview(ctx);
	}
}

static void tree_del_cb(void *hid_ctx, void *caller_data, rnd_hid_attribute_t *attr)
{
	tree_dlg_ctx_t *ctx = caller_data;
	csch_chdr_t *obj = get_dlg_obj(ctx, 0);

	if (obj != NULL) {
		csch_op_remove(obj->sheet, obj);
		tree_update_preview(ctx);
	}
}


static void tree_anyedit_cb(void *hid_ctx, void *caller_data, rnd_hid_attribute_t *attr, const char *actname)
{
	tree_dlg_ctx_t *ctx = caller_data;
	csch_chdr_t *obj = get_dlg_obj(ctx, 0);

	if (obj != NULL) {
		rnd_design_t *hl = &obj->sheet->hidlib;
		char *arg, *oidp;

		oidp = csch_chdr_to_oidpath_str(obj);
		arg = rnd_concat("object:", oidp, NULL);
		free(oidp);

		rnd_actionva(hl, actname, arg, NULL);

		free(arg);
	}
}

static void tree_propedit_cb(void *hid_ctx, void *caller_data, rnd_hid_attribute_t *attr)
{
	tree_anyedit_cb(hid_ctx, caller_data, attr, "Propedit");
}

static void tree_attredit_cb(void *hid_ctx, void *caller_data, rnd_hid_attribute_t *attr)
{
	tree_anyedit_cb(hid_ctx, caller_data, attr, "AttributeDialog");
}

static void tree_prv_expose_cb(rnd_hid_attribute_t *attrib, rnd_hid_preview_t *prv, rnd_hid_gc_t gc, rnd_hid_expose_ctx_t *e)
{
	tree_dlg_ctx_t *ctx = prv->user_ctx;
	rnd_xform_t xform = {0};
	csch_chdr_t *obj = get_dlg_obj(ctx, 1);
	int save, n;

	/* draw the sheet */
	if (obj != NULL) {
		xform.faded = 1;
		save = obj->hilight;
		obj->hilight = 1;

		hisave.used = 0;
		if (obj->type == CSCH_CTYPE_CONN) {
			csch_conn_t *conn = (csch_conn_t *)obj;
			for(n = 0; n < conn->conn.used; n++) {
				csch_chdr_t *co = conn->conn.array[n];
				vtl0_append(&hisave, co->hilight);
				co->hilight = 1;
			}
		}
	}

	if (obj != NULL)
		e->design = &obj->sheet->hidlib;
	else
		e->design = rnd_gui->get_dad_design(ctx->dlg_hid_ctx);

	rnd_app.expose_main(rnd_gui, e, &xform);

	if (obj != NULL) {
		obj->hilight = save;
		if (obj->type == CSCH_CTYPE_CONN) {
			csch_conn_t *conn = (csch_conn_t *)obj;
			for(n = 0; n < conn->conn.used; n++) {
				csch_chdr_t *co = conn->conn.array[n];
				co->hilight = hisave.array[n];
			}
		}
	}
}


static rnd_bool tree_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 tree_set_cursor(tree_dlg_ctx_t *ctx, const char *oidpath)
{
	rnd_hid_attribute_t *attr = &ctx->dlg[ctx->wtree];
	rnd_hid_tree_t *tree = attr->wdata;
	rnd_hid_row_t *row;

	if (*oidpath == '/') oidpath++;

	TODO("multi: oidpath will need to start with a sheet uid that can be searched in the current project (linear search is good enough)");
	row = htsp_get(&tree->paths, oidpath);

	rnd_trace("tree set cursor to '%s' %p\n", oidpath, row);
	if (row != NULL) {
		rnd_hid_attr_val_t hv;
		hv.str = oidpath;
		rnd_gui->attr_dlg_set_value(ctx->dlg_hid_ctx, ctx->wtree, &hv);
		tree_update_details(ctx, row);
	}
	else
		rnd_message(RND_MSG_ERROR, "tree view: oid path not found: '%s'\n", oidpath);
}

static void tree_dlg_set_objarr(tree_dlg_ctx_t *ctx, vtp0_t *objarr)
{
	long n;

	if (!ctx->only_vis) {
		htpi_init(&ctx->only, ptrhash, ptrkeyeq);
		ctx->only_vis = 1;
	}
	else
		htpi_clear(&ctx->only);

	for(n = 0; n < objarr->used; n++) {
		csch_chdr_t *o = objarr->array[n];

		htpi_set(&ctx->only, o, OBJ_ST_DIRECT);

		/* add all parents too so that group recursion works on update() */
		for(o = &o->parent->hdr; o != NULL; o = &o->parent->hdr) {
			if (!htpi_has(&ctx->only, o))
				htpi_set(&ctx->only, o, OBJ_ST_INDIRECT);
		}
	}
}

static void sch_rnd_tree_dlg(csch_project_t *prj, csch_sheet_t *oidpath_sheet, const char *oidpath, vtp0_t *objarr)
{
	tree_dlg_ctx_t *ctx;
	rnd_hid_dad_buttons_t clbtn[] = {{"Close", 0}, {NULL, 0}};
	const char *hdr[] = {"oid path", "type", "subtype", "name", NULL};
	char *freeme = NULL;

	ctx = htpp_get(&prj2dlg, prj);
	if (ctx != NULL) {
		if (objarr != NULL) {
			tree_dlg_set_objarr(ctx, objarr);
			tree_update(ctx);
		}
		if (oidpath != NULL)
			tree_set_cursor(ctx, oidpath);
		TODO("raise?");
		return;
	}

	if ((oidpath != NULL) && (oidpath[0] != '{')) {
		char tmp[64];

		sprintf(tmp, "{%ld} ", oidpath_sheet->uid);
		if (*oidpath == '/')
			oidpath++;
		oidpath = freeme = rnd_concat(tmp, oidpath, NULL);
	}

	ctx = calloc(sizeof(tree_dlg_ctx_t), 1);
	ctx->prj = prj;

	/* create hash of only visible objects, if requested */
	if (objarr != NULL)
		tree_dlg_set_objarr(ctx, objarr);

	tree_preview_bbox(ctx, NULL);

	htpp_set(&prj2dlg, prj, ctx);

	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_TREE(ctx->dlg, 4, 1, hdr); /* top left: tree */
					RND_DAD_COMPFLAG(ctx->dlg, RND_HATF_EXPFILL | RND_HATF_SCROLL | RND_HATF_TREE_NO_AUTOEXP);
					RND_DAD_TREE_SET_CB(ctx->dlg, selected_cb, tree_select_node_cb);
					RND_DAD_TREE_SET_CB(ctx->dlg, ctx, ctx);
					ctx->wtree = RND_DAD_CURRENT(ctx->dlg);
				RND_DAD_BEGIN_HBOX(ctx->dlg); /* bottom left: buttons */
					RND_DAD_BUTTON(ctx->dlg, "select");
						RND_DAD_CHANGE_CB(ctx->dlg, tree_select_cb);
					RND_DAD_BUTTON(ctx->dlg, "unselect");
						RND_DAD_CHANGE_CB(ctx->dlg, tree_unselect_cb);
					RND_DAD_BUTTON(ctx->dlg, "propedit");
						RND_DAD_CHANGE_CB(ctx->dlg, tree_propedit_cb);
					RND_DAD_BUTTON(ctx->dlg, "attr. edit");
						RND_DAD_CHANGE_CB(ctx->dlg, tree_attredit_cb);
						ctx->wattredit = RND_DAD_CURRENT(ctx->dlg);
					RND_DAD_BUTTON(ctx->dlg, "del");
						RND_DAD_CHANGE_CB(ctx->dlg, tree_del_cb);
				RND_DAD_END(ctx->dlg);
			RND_DAD_LABEL(ctx->dlg, "Pending update...");
				ctx->wupdate = RND_DAD_CURRENT(ctx->dlg);
			RND_DAD_END(ctx->dlg);

			RND_DAD_BEGIN_VPANE(ctx->dlg, "right_top/bottom"); /* right */
				RND_DAD_COMPFLAG(ctx->dlg, RND_HATF_EXPFILL);

				RND_DAD_PREVIEW(ctx->dlg, tree_prv_expose_cb, tree_prv_mouse_cb, NULL, NULL, &ctx->prvbb, 150, 150, ctx);
					RND_DAD_COMPFLAG(ctx->dlg, RND_HATF_PRV_GFLIP);
					ctx->wprev = RND_DAD_CURRENT(ctx->dlg);

				RND_DAD_BEGIN_VBOX(ctx->dlg); /* right bottom text/close */
					RND_DAD_COMPFLAG(ctx->dlg, RND_HATF_EXPFILL);
					RND_DAD_LABEL(ctx->dlg, "(no object picked)");
						ctx->wdetails = RND_DAD_CURRENT(ctx->dlg);

					RND_DAD_BEGIN_VBOX(ctx->dlg); /* spring */
						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_DEFSIZE(ctx->dlg, 300, 400);
	RND_DAD_NEW("TreeDialog", ctx->dlg, "Tree edit", ctx, 0, tree_dlg_close_cb); /* type=local/project */

	rnd_gui->attr_dlg_widget_hide(ctx->dlg_hid_ctx, ctx->wupdate, 1);
	rnd_gui->attr_dlg_widget_state(ctx->dlg_hid_ctx, ctx->wattredit, 0);
	tree_update(ctx);
	if (oidpath != NULL) {
		tree_set_cursor(ctx, oidpath);
		tree_update_preview(ctx); /* set cursor from code will not call back, need to manually make side effects */
	}
	free(freeme);
}

static void tree_timer_cb(rnd_hidval_t user_data)
{
	tree_dlg_ctx_t *ctx = user_data.ptr;
	ctx->timer_active = 0;
	rnd_gui->attr_dlg_widget_hide(ctx->dlg_hid_ctx, ctx->wupdate, 1);
	tree_update(ctx);
}

static void csch_dlg_tree_regen_(csch_sheet_t *sheet, int timeout)
{
	rnd_hidval_t user_data;
	tree_dlg_ctx_t *ctx = htpp_get(&prj2dlg, (csch_project_t *)sheet->hidlib.project);

	if (ctx == NULL) return;

	if (ctx->timer_active)
		rnd_gui->stop_timer(rnd_gui, ctx->timer);

	rnd_gui->attr_dlg_widget_hide(ctx->dlg_hid_ctx, ctx->wupdate, 0);

	user_data.ptr = ctx;
	ctx->timer_active = 1;
	ctx->timer = rnd_gui->add_timer(rnd_gui, tree_timer_cb, timeout, user_data);
}

void csch_dlg_tree_edit(csch_sheet_t *sheet)
{
	csch_dlg_tree_regen_(sheet, CSCH_TREE_CHG_TIMEOUT_NORMAL);
}

void csch_dlg_tree_chg_sheet(csch_sheet_t *sheet)
{
	csch_dlg_tree_regen_(sheet, CSCH_TREE_CHG_TIMEOUT_QUICK);
}

void csch_dlg_tree_prj_compiled(csch_project_t *project)
{
	tree_dlg_ctx_t *ctx = htpp_get(&prj2dlg, project);
	if (ctx != NULL)
		tree_update_preview(ctx);
}


const char csch_acts_TreeDialog[] = "TreeDialog([object[=idpath]|objarr,vtp0ptr])";
const char csch_acth_TreeDialog[] = "Bring up the sheet's object tree dialog. If object is specified, move cursor to that object in the tree.\n";
fgw_error_t csch_act_TreeDialog(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;
	int cmd = -1;
	const char *cmds = "", *oidps;

	RND_ACT_MAY_CONVARG(1, FGW_STR, TreeDialog, cmds = argv[1].val.str);

	oidps = strpbrk(cmds, ":=");
	if (oidps != NULL) {
		if (strncmp(cmds, "object", 6) == 0) {
			oidps++;
		}
		else {
			rnd_message(RND_MSG_ERROR, "Invalid first arg in TreeDialog\n");
			return FGW_ERR_ARG_CONV;
		}
	}
	else
		cmd = rnd_funchash_get(cmds, NULL);

	RND_ACT_IRES(-1);

	switch(cmd) {
		case -1:
			sch_rnd_tree_dlg((csch_project_t *)sheet->hidlib.project, sheet, oidps, NULL);
			break;
		case F_Object:
			{
				csch_chdr_t *obj;
				csch_coord_t x, y;

				if (sch_rnd_get_coords("Click on object to view in tree", &x, &y, 0) != 0)
					break;

				obj = sch_rnd_search_obj_at(sheet, x, y, sch_rnd_slop);
				if (obj != NULL) {
					sheet = obj->sheet;
					sch_rnd_tree_dlg((csch_project_t *)sheet->hidlib.project, sheet, csch_chdr_to_oidpath_str(obj), NULL);
				}
				else
					rnd_message(RND_MSG_ERROR, "TreeDialog(): no object under cursor\n");
			}
			break;
		case F_Objarr:
			{
				vtp0_t *arr = argv[2].val.ptr_void;
				if ((argv[2].type != (FGW_PTR | FGW_STRUCT)) || (!fgw_ptr_in_domain(&rnd_fgw, &argv[2], CSCH_PTR_DOMAIN_COBJ_ARR))) {
					rnd_message(RND_MSG_ERROR, "TreeDialog(): objarr argument is not an object arr pointer\n");
					break;
				}
				sch_rnd_tree_dlg((csch_project_t *)sheet->hidlib.project, NULL, NULL, arr);
			}
			break;
		default:
			rnd_message(RND_MSG_ERROR, "TreeDialog(): invalid first argument\n");
	}

	return 0;
}

void csch_dlg_tree_init(void)
{
	htpp_init(&prj2dlg, ptrhash, ptrkeyeq);
	vtl0_init(&hisave);
}

void csch_dlg_tree_uninit(void)
{
	rnd_dad_retovr_t retovr = {0};
	htpp_entry_t *e;

	vtl0_uninit(&hisave);

	for(e = htpp_first(&prj2dlg); e != NULL; e = htpp_next(&prj2dlg, e)) {
		tree_dlg_ctx_t *ctx = e->value;
		rnd_hid_dad_close(ctx->dlg_hid_ctx, &retovr, 0);
	}

	htpp_uninit(&prj2dlg);
}
