#include "pivaluetree_edit.h"

#include "pijson.h"
#include "piqt.h"
#include "pivaluetree_conversions.h"
#include "pivaluetree_edit_parameters.h"
#include "pivaluetree_edit_reorder.h"
#include "pivariant_edit.h"
#include "ui_pivaluetree_edit_array.h"

#include <QClipboard>
#include <QEvent>
#include <QFormLayout>
#include <QGroupBox>
#include <QInputDialog>
#include <QMessageBox>
#include <QTabWidget>
#include <QToolButton>

using Attribute            = PIValueTree::Attribute;

const char property_name[] = "__name__";


class ToolButton: public QToolButton {
public:
	ToolButton(QWidget * parent = nullptr): QToolButton(parent) {}
	void setPressHandler(std::function<void()> f) { press_handler = f; }

protected:
	void mousePressEvent(QMouseEvent * e) override {
		if (press_handler) press_handler();
		QToolButton::mousePressEvent(e);
	}
	std::function<void()> press_handler;
};


class GroupBox: public QGroupBox {
public:
	GroupBox(QWidget * content = nullptr): QGroupBox() {
		icon_show = QIcon(":/icons/layer-visible-on.png");
		icon_hide = QIcon(":/icons/layer-visible-off.png");
		setLayout(new QBoxLayout(QBoxLayout::TopToBottom));
		layout()->addWidget(content);
		setAlignment(Qt::AlignCenter);
		btn = new QToolButton(this);
		btn->setCheckable(true);
		btn->show();
		connect(btn, &QToolButton::toggled, this, [this, content](bool on) {
			btn->setIcon(on ? icon_show : icon_hide);
			content->setVisible(on);
		});
		btn->setChecked(true);
	}

private:
	void resizeEvent(QResizeEvent * e) {
		QGroupBox::resizeEvent(e);
		btn->resize(btn->height(), btn->height());
		btn->move(btn->height() / 2, 0);
	}
	QToolButton * btn = nullptr;
	QIcon icon_show, icon_hide;
};


PIValueTreeEdit::PIValueTreeEdit(QWidget * parent): QWidget(parent) {
	widget_params  = new PIValueTreeEditParameters();
	widget_reorder = new PIValueTreeEditReorder();
	ui_array       = new Ui::PIValueTreeEditArray();
	grid           = new GridWidgets(this);
	auto * lay     = new QBoxLayout(QBoxLayout::TopToBottom);
	lay->setContentsMargins(0, 0, 0, 0);
	setLayout(lay);
}


PIValueTreeEdit::~PIValueTreeEdit() {
	delete grid;
	delete ui_array;
	delete widget_params;
}


void PIValueTreeEdit::setValue(const PIValueTree & v) {
	current = source = v;
	build();
}


PIValueTree PIValueTreeEdit::value() const {
	applyValues();
	return current;
}


void PIValueTreeEdit::setGrouping(Grouping g) {
	applyValues();
	cur_grouping = g;
	for (auto * a: widget_params->menu_grouping.actions()) {
		if (a->data().toInt() == g) {
			a->setChecked(true);
			break;
		}
	}
	build();
}


void PIValueTreeEdit::setFullEditMode(bool yes) {
	applyValues();
	is_full_edit = yes;
	build();
}


void PIValueTreeEdit::setReadOnly(bool yes) {
	applyValues();
	m_read_only = yes;
	build();
}


void PIValueTreeEdit::setPermissions(Permissions p) {
	perm = p;
	applyPermissions();
}


void PIValueTreeEdit::setPermission(Permission p, bool on) {
	perm.setFlag(p, on);
	applyPermissions();
}


void PIValueTreeEdit::rollback() {
	current = source;
	build();
}


void PIValueTreeEdit::clear() {
	current = PIValueTree();
	removeAll();
}


void PIValueTreeEdit::retranslate() {
	for (const auto & i: value_edits)
		i.second->retranslate();
	for (const auto & i: tree_edits)
		i.second->retranslate();
	for (const auto & i: comm_labels) {
		i.second->setText(PIVariantEditorBase::vtTr(current.child(i.first).comment()));
	}
	for (const auto & i: label_labels) {
		i.second->setText(PIVariantEditorBase::vtTr(i.first));
	}
	if (tab_widget) {
		for (int i = 0; i < tab_widget->count(); ++i) {
			tab_widget->setTabText(i, PIVariantEditorBase::vtTr(Q2PIString(tab_widget->tabBar()->tabData(i).toString())));
		}
	}
	grid->retranslate();
}


void PIValueTreeEdit::changeEvent(QEvent * e) {
	if (e->type() == QEvent::LanguageChange) {
		if (widget_array) ui_array->retranslateUi(widget_array);
		retranslate();
	}
	QWidget::changeEvent(e);
}


void PIValueTreeEdit::removeAll() {
	array_edits.clear();
	value_edits.clear();
	tree_edits.clear();
	comm_labels.clear();
	label_labels.clear();
	tab_widget = nullptr;
	if (widget_array) {
		ui_array->layoutArray->removeWidget(grid);
		grid->hide();
		grid->setParent(this);
		delete widget_array;
		widget_array = nullptr;
	}
	QLayoutItem * child = nullptr;
	while ((child = layout()->takeAt(0))) {
		delete child;
	}
	grid->clear();
}


void PIValueTreeEdit::build() {
	grid->create_edit_buttons = false;
	removeAll();
	// piCout << source.attributes().value(Attribute::arrayType) << array_type;
	grid->button_add->hide();
	if (current.isArray()) {
		widget_array = new QWidget();
		ui_array->setupUi(widget_array);
		applyArrayAttributes();
		ui_array->layoutArray->addWidget(grid);
		grid->show();
		uint array_type = PIVariant::typeIDFromName(current.attribute(Attribute::arrayType).toString());
		int index       = 0;
		for (const auto & i: current.children()) {
			auto * ve = new PIVariantEdit();
			ve->setAttributes(attributesWithRO(current.attributes()));
			ve->setValue(i.value(), array_type);
			grid->add(PIValueTree({PIString::fromNumber(index), PIVariant()}), PIString::fromNumber(index + 1), ve, i.comment());
			++index;
			array_edits << ve;
		}
		connect(ui_array->spinCount, QOverload<int>::of(&QSpinBox::valueChanged), this, [this](int) { PIValueTreeEdit::resizeArray(); });
		layout()->addWidget(widget_array);
	} else {
		grid->create_edit_buttons = is_full_edit;
		layout()->addWidget(grid);
		if (!current.hasChildren()) grid->clear();
		for (const auto & i: current.children()) {
			if (i.attribute(Attribute::hidden, false).toBool() && !is_full_edit) continue;
			if (i.attribute(Attribute::isLabel, false).toBool()) {
				if (i.name().isEmpty()) continue;
				auto * l = newLabel(i);
				grid->add(i, l);
				continue;
			}
			if (i.hasChildren() || i.isArray()) {
				addTreeEdit(i);
			} else {
				addValueEdit(i);
			}
		}
	}
}


void PIValueTreeEdit::applyValues() const {
	if (current.isArray()) {
		if (array_edits.isNotEmpty()) current.mergeAttributes(array_edits[0]->attributes());
		current.clearChildren();
		for (int i = 0; i < array_edits.size_s(); ++i) {
			PIValueTree vt(PIString::fromNumber(i), array_edits[i]->value());
			auto comm_lbl = comm_labels.value(vt.name());
			if (comm_lbl) vt.setComment(Q2PIString(comm_lbl->text()));
			current.addChild(vt);
		}
	} else {
		auto vit = value_edits.makeIterator();
		while (vit.next()) {
			auto & c(current.child(vit.key()));
			c.mergeAttributes(vit.value()->attributes());
			c.setValue(vit.value()->value());
		}
		auto tit = tree_edits.makeIterator();
		while (tit.next()) {
			auto & c(current.child(tit.key()));
			if (!c.isNull()) c = tit.value()->value();
		}
		if (current.hasChildren()) {
			auto ge = PIVariantEditorBase::createGrouping();
			ge.selectValue(cur_grouping);
			current.setAttribute(Attribute::grouping, ge);
		}
	}
}


void PIValueTreeEdit::applyPermissions() {
	auto * wp = widget_params;
	wp->actionValue->setVisible(perm.testFlag(AddValue));
	wp->actionGroup->setVisible(perm.testFlag(AddGroup));
	wp->actionArray->setVisible(perm.testFlag(AddArray));
	wp->actionCut->setVisible(perm.testFlag(CanCut));
	wp->actionCopy->setVisible(perm.testFlag(CanCopy));
	wp->actionPaste->setVisible(perm.testFlag(CanPaste));
	wp->actionPasteAfter->setVisible(perm.testFlag(CanPaste));
	wp->actionPasteBefore->setVisible(perm.testFlag(CanPaste));
	wp->checkHidden->setVisible(perm.testFlag(ChangeHidden));
	wp->checkLabel->setVisible(perm.testFlag(ChangeLabel));
	wp->checkReadOnly->setVisible(perm.testFlag(ChangeReadOnly));
	wp->lineComment->setVisible(perm.testFlag(ChangeComment));
	wp->labelComment->setVisible(perm.testFlag(ChangeComment));
	wp->comboType->setVisible(perm.testFlag(ChangeType));
	wp->labelType->setVisible(perm.testFlag(ChangeType));
	wp->menu_grouping.menuAction()->setVisible(perm.testFlag(ChangeGrouping));
	wp->actionRename->setVisible(perm.testFlag(CanRename));
	wp->actionRemove->setVisible(perm.testFlag(CanRemove));
	wp->actionReorder->setVisible(perm.testFlag(CanReorder));
	for (auto * c: {wp->checkHidden, wp->checkLabel, wp->checkReadOnly})
		if (!c->isEnabled()) c->setChecked(false);
	grid->button_add->setVisible(
		is_full_edit && PIVector<QAction *>({wp->actionValue, wp->actionGroup, wp->actionArray, wp->actionPaste}).any([](QAction * a) {
			return a->isVisible();
		}));
	for (auto c: tree_edits)
		c.second->setPermissions(perm);
}


void PIValueTreeEdit::actionRename(QToolButton * button, const PIString & vn) {
	PIString nn = Q2PIString(QInputDialog::getText(nullptr, tr("Rename"), tr("Input new name:"), QLineEdit::Normal, PI2QString(vn)));
	if (nn.isEmpty() || (nn == vn)) return;
	for (const auto & c: current.children()) {
		if (c.name() == nn) {
			QMessageBox::critical(nullptr, tr("Rename"), tr("This name already exists!"));
			return;
		}
	}
	current[vn].setName(nn);
	button->setProperty(property_name, PI2QString(nn));
	grid->rename(vn, nn);
	if (value_edits.contains(vn)) {
		value_edits[nn] = value_edits[vn];
		value_edits.remove(vn);
	}
	if (tree_edits.contains(vn)) {
		tree_edits[nn] = tree_edits[vn];
		tree_edits.remove(vn);
	}
	if (comm_labels.contains(vn)) {
		comm_labels[nn] = comm_labels[vn];
		comm_labels.remove(vn);
	}
	if (label_labels.contains(vn)) {
		label_labels[nn] = label_labels[vn];
		label_labels[nn]->setText(PIVariantEditorBase::vtTr(nn));
		label_labels.remove(vn);
	}
	if (tab_widget) {
		for (int i = 0; i < tab_widget->count(); ++i) {
			if (tab_widget->tabBar()->tabData(i).toString() == PI2QString(vn)) {
				tab_widget->setTabText(i, PIVariantEditorBase::vtTr(nn));
				tab_widget->tabBar()->setTabData(i, PI2QString(nn));
				break;
			}
		}
	}
}


void PIValueTreeEdit::actionRemove(QToolButton * button, const PIString & vn) {
	current.remove(vn);
	if (tab_widget) {
		QString qvn = PI2QString(vn);
		QMetaObject::invokeMethod(
			this,
			[this, qvn]() {
				for (int i = 0; i < tab_widget->count(); ++i) {
					if (tab_widget->tabBar()->tabData(i).toString() == qvn) {
						tab_widget->removeTab(i);
						break;
					}
				}
				if (tab_widget->count() == 0) {
					grid->removeRow(grid->getRow(tab_widget));
					tab_widget = nullptr;
				}
			},
			Qt::QueuedConnection);
	}
	grid->removeRow(grid->getRow(button));
	value_edits.remove(vn);
	tree_edits.remove(vn);
	comm_labels.remove(vn);
	label_labels.remove(vn);
}


void PIValueTreeEdit::actionChange(QToolButton * button, const PIString & vn) {
	auto & vt(current[vn]);
	if (vt.isArray()) {
		auto * ve = tree_edits.value(vn, nullptr);
		if (!ve) return;
		vt = ve->value();
		if (!widget_params->showFor(vt)) return;
		ve->setValue(vt);
		ve->resizeArray();
		// ve->applyArrayAttributes();
	} else {
		bool was_label = vt.attribute(Attribute::isLabel, false).toBool();
		auto * ve      = value_edits.value(vn, nullptr);
		if (ve) {
			vt.setValue(ve->value());
			vt.mergeAttributes(ve->attributes());
		}
		if (!widget_params->showFor(vt)) return;
		bool now_label = vt.attribute(Attribute::isLabel, false).toBool();
		if (was_label ^ now_label) {
			if (now_label) {
				auto * l = newLabel(vt);
				grid->replace(grid->getRow(button), l);
				value_edits.remove(vt.name());
				comm_labels.remove(vt.name());
			} else {
				auto * ve = new PIVariantEdit();
				applyVariantEdit(ve, vt);
				grid->replace(grid->getRow(button), vt.name(), ve, vt.comment());
				value_edits[vt.name()] = ve;
			}
			ve = nullptr;
		}
		if (ve) {
			applyVariantEdit(ve, vt);
		}
		if (now_label) {
			label_labels[vt.name()]->setStyleSheet(PI2QString(vt.attribute(Attribute::style).toString()));
		}
	}
	auto * cl = comm_labels.value(vn, nullptr);
	if (cl) cl->setText(PIVariantEditorBase::vtTr(vt.comment()));
}


void PIValueTreeEdit::actionReorder(QToolButton *, const PIString & vn) {
	if (!widget_reorder->showFor(current)) return;
	grid->reorder(widget_reorder->map);
	auto cl = current.children();
	current.clearChildren();
	for (int i = 0; i < cl.size_s(); ++i) {
		int mi = widget_reorder->map.value(i, i);
		if (mi < 0 || mi >= cl.size_s()) continue;
		current.addChild(cl[mi]);
	}
}


void PIValueTreeEdit::actionCopy(QToolButton *, const PIString & vn) {
	PIValueTree cur_val = value();
	auto & vt(cur_val[vn]);
	// piCout << vt;
	PIString json =
		PIValueTreeConversions::toJSON(vt, PIValueTreeConversions::Default | PIValueTreeConversions::IncludeRoot).toJSON(PIJSON::Tree);
	QApplication::clipboard()->setText(PI2QString(json));
}


void PIValueTreeEdit::actionPaste(QToolButton *, const PIString & vn, int offset) {
	PIString json = Q2PIString(QApplication::clipboard()->text());
	if (json.isEmpty()) return;
	while (current.contains(paste_name)) {
		bool ok   = false;
		QString s = QInputDialog::getText(nullptr,
										  tr("Paste"),
										  tr("Name \"%1\" already exists, get new name:").arg(PI2QString(paste_name)),
										  QLineEdit::Normal,
										  PI2QString(paste_name),
										  &ok);
		if (!ok) return;
		paste_name = Q2PIString(s);
	}
	PIValueTree ins_val = PIValueTreeConversions::fromJSON(PIJSON::fromJSON(json));
	ins_val.setName(paste_name);
	// if (ins_val.name.isEmpty()) return;
	PIValueTree cur_val = value();
	cur_val.insertChild(cur_val.childIndex(vn) + offset, ins_val);
	setValue(cur_val);
}


void PIValueTreeEdit::actionTriggered(QToolButton * button, const PIString & vn, QAction * a) {
	if (a == widget_params->actionRename) {
		actionRename(button, vn);
		return;
	}
	if (a == widget_params->actionRemove) {
		actionRemove(button, vn);
		return;
	}
	if (a == widget_params->actionChange) {
		actionChange(button, vn);
		return;
	}
	if (a == widget_params->actionReorder) {
		actionReorder(button, vn);
		return;
	}
	if (a == widget_params->actionCut) {
		actionCopy(button, vn);
		actionRemove(button, vn);
		return;
	}
	if (a == widget_params->actionCopy) {
		actionCopy(button, vn);
		return;
	}
	if (a == widget_params->actionPasteBefore) {
		actionPaste(button, vn, 0);
		return;
	}
	if (a == widget_params->actionPasteAfter) {
		actionPaste(button, vn, 1);
		return;
	}
	setGrouping((Grouping)a->data().toInt());
}


void PIValueTreeEdit::checkActions() {
	paste_name.clear();
	bool can_paste = true;
	PIString str   = Q2PIString(QApplication::clipboard()->text());
	if (str.isEmpty() || !str.startsWith("{")) {
		can_paste = false;
	} else {
		PIJSON j = PIJSON::fromJSON(str);
		if (!j.contains("name"))
			can_paste = false;
		else {
			paste_name = j["name"].value().toString();
		}
	}
	widget_params->actionPasteBefore->setEnabled(can_paste);
	widget_params->actionPasteAfter->setEnabled(can_paste);
	widget_params->actionPaste->setEnabled(can_paste);
}


PIValueTreeEdit * PIValueTreeEdit::addTreeEdit(const PIValueTree & vt) {
	auto * ve       = new PIValueTreeEdit();
	PIStringList rp = root_path;
	rp << vt.name();
	ve->root_path   = rp;
	ve->parent_tree = this;
	ve->m_read_only = m_read_only;
	ve->setGrouping((Grouping)vt.attribute(Attribute::grouping, PIVariantEditorBase::createGrouping()).toEnum().selectedValue());
	ve->setFullEditMode(is_full_edit);
	ve->setPermissions(permissions());
	ve->setValue(vt);
	switch (cur_grouping) {
	case Indent: grid->add(vt, vt.name(), ve, vt.comment(), true); break;
	case Groups: {
		auto * gb = new GroupBox(ve);
		gb->setTitle(PIVariantEditorBase::vtTr(vt.name()));
		gb->setToolTip(PIVariantEditorBase::vtTr(vt.comment()));
		gb->setProperty(property_name, PI2QString(vt.name()));
		grid->add(vt, gb, true);
	} break;
	case Tabs: {
		createTabWidget();
		auto * cw = new QWidget();
		cw->setLayout(new QBoxLayout(QBoxLayout::TopToBottom));
		cw->layout()->addWidget(ve);
		cw->layout()->addItem(new QSpacerItem(0, 0, QSizePolicy::Preferred, QSizePolicy::Expanding));
		int tab = tab_widget->addTab(cw, PIVariantEditorBase::vtTr(vt.name()));
		tab_widget->setTabToolTip(tab, PIVariantEditorBase::vtTr(vt.comment()));
		tab_widget->tabBar()->setTabData(tab, PI2QString(vt.name()));
		if (is_full_edit) {
			auto * b = grid->createConfigButton(vt, true);
			tab_widget->tabBar()->setTabButton(tab, QTabBar::RightSide, b);
		}
	} break;
	default: break;
	}
	tree_edits[vt.name()] = ve;
	return ve;
}


PIValueTreeEdit * PIValueTreeEdit::rootTreeEdit() {
	PIValueTreeEdit * ret = this;
	while (ret->parent_tree)
		ret = ret->parent_tree;
	return ret;
}


void PIValueTreeEdit::addValueEdit(const PIValueTree & vt) {
	auto * ve = new PIVariantEdit();
	applyVariantEdit(ve, vt);
	grid->add(vt, vt.name(), ve, vt.comment());
	value_edits[vt.name()] = ve;
}


void PIValueTreeEdit::applyArrayAttributes() {
	ui_array->spinCount->setRange(current.attribute(Attribute::arrayMinCount, 0).toInt(),
								  current.attribute(Attribute::arrayMaxCount, 65536).toInt());
	ui_array->spinCount->setValue(current.children().size_s());
	ui_array->widgetEdit->setVisible(current.attribute(Attribute::arrayResize, false).toBool());
	uint array_type = PIVariant::typeIDFromName(current.attribute(Attribute::arrayType).toString());
	for (int i = 0; i < array_edits.size_s(); ++i) {
		auto * w = array_edits[i];
		w->setAttributes(attributesWithRO(current.attributes()));
		w->setValue(i < current.children().size_s() ? current.children()[i].value() : PIVariant(), array_type);
	}
}


QLabel * PIValueTreeEdit::newLabel(const PIValueTree & vt) {
	auto * l = new QLabel();
	l->setAlignment(Qt::AlignCenter);
	l->setText(PIVariantEditorBase::vtTr(vt.name()));
	l->setStyleSheet(PI2QString(vt.attribute(Attribute::style).toString()));
	label_labels[vt.name()] = l;
	return l;
}


void PIValueTreeEdit::applyVariantEdit(PIVariantEdit * ve, const PIValueTree & vt) {
	ve->setAttributes(attributesWithRO(vt.attributes()));
	ve->setValue(vt.value());
	ve->setFullEditMode(is_full_edit);
	ve->setToolTip(PIVariantEditorBase::vtTr(vt.attribute(PIValueTree::Attribute::toolTip).toString()));
}


void PIValueTreeEdit::createTabWidget() {
	if (tab_widget) return;
	tab_widget = new QTabWidget();
	grid->addRow(tab_widget);
}


void PIValueTreeEdit::resizeArray() {
	if (!ui_array) return;
	uint array_type = PIVariant::typeIDFromName(current.attribute(Attribute::arrayType).toString());
	int value       = piMaxi(ui_array->spinCount->value(), 0);
	for (int i = grid->rowCount() - 1; i >= value; --i)
		grid->removeRow(i);
	array_edits.resize(grid->rowCount());
	for (int i = grid->rowCount(); i < value; ++i) {
		auto * ve = new PIVariantEdit();
		ve->setAttributes(attributesWithRO(current.attributes()));
		ve->setValue(PIVariant::fromType(array_type), array_type);
		grid->add(PIValueTree(), PIString::fromNumber(i + 1), ve, "");
		array_edits << ve;
	}
}


PIVariantMap PIValueTreeEdit::attributesWithRO(const PIVariantMap & attr) {
	PIVariantMap ret = attr;
	if (m_read_only) ret[PIValueTree::Attribute::readOnly] = true;
	return ret;
}


void PIValueTreeEdit::newRequest(NewType type) {
	PIString nn;
	if (rootTreeEdit()->allowed_names && (type == NewType::Value)) {
		QStringList anl = PI2QStringList(rootTreeEdit()->allowed_names());
		if (anl.isEmpty()) {
			QMessageBox::warning(nullptr, tr("New item"), tr("No allowed names!"));
			return;
		}
		bool ok = false;
		nn      = Q2PIString(QInputDialog::getItem(nullptr, tr("New item"), tr("Select new name:"), anl, 0, false, &ok));
		if (!ok) return;
	} else {
		nn = Q2PIString(QInputDialog::getText(nullptr, tr("New item"), tr("Input new name:")));
	}
	if (nn.isEmpty()) return;
	for (const auto & c: current.children()) {
		if (c.name() == nn) {
			QMessageBox::critical(nullptr, tr("New item"), tr("This name already exists!"));
			return;
		}
	}
	PIValueTree vt;
	vt.setName(nn);
	if (type == NewType::Value) {
		if (rootTreeEdit()->value_by_name) {
			vt.setValue(rootTreeEdit()->value_by_name(nn));
		}
		if (!widget_params->showFor(vt)) return;
	}
	if (type == NewType::Array) {
		vt.setAttribute(Attribute::arrayType, PIVariant::typeName<PIString>());
		if (!widget_params->showFor(vt)) return;
	}
	current.addChild(vt);
	switch (type) {
	case NewType::Value:
		if (vt.attribute(Attribute::isLabel, false).toBool()) {
			auto * l = newLabel(vt);
			grid->add(vt, l);
		} else {
			addValueEdit(vt);
		}
		break;
	case NewType::Group: addTreeEdit(vt); break;
	case NewType::Array: addTreeEdit(vt)->resizeArray(); break;
	}
}


// PIValueTreeEdit::GridWidgets

PIValueTreeEdit::GridWidgets::GridWidgets(PIValueTreeEdit * p) {
	parent            = p;
	icon_conf         = QIcon(":/icons/configure.png");
	auto newSeparator = []() {
		auto * a = new QAction();
		a->setSeparator(true);
		return a;
	};
	auto * wp = p->widget_params;
	auto common_actions =
		{newSeparator(), wp->actionCut, wp->actionCopy, wp->actionPasteBefore, wp->actionPasteAfter, newSeparator(), wp->actionRemove};
	menu_group.addActions({wp->actionRename, wp->actionReorder, wp->menu_grouping.menuAction()});
	menu_conf.addActions({wp->actionRename, wp->actionChange, wp->actionReorder, wp->menu_grouping.menuAction()});
	menu_group.addActions(common_actions);
	menu_conf.addActions(common_actions);
	menu_new.addActions({wp->actionValue, wp->actionGroup, wp->actionArray, newSeparator(), wp->actionPaste});
	button_add = new ToolButton(parent);
	button_add->setIcon(QIcon(":/icons/list-add.png"));
	button_add->setPopupMode(QToolButton::InstantPopup);
	button_add->setMenu(&menu_new);
	button_add->hide();
	((ToolButton *)button_add)->setPressHandler([this]() { parent->checkActions(); });
	wp->actionValue->setData((int)NewType::Value);
	wp->actionGroup->setData((int)NewType::Group);
	wp->actionArray->setData((int)NewType::Array);
	connect(button_add, &QToolButton::triggered, this, [this](QAction * a) {
		if (a == parent->widget_params->actionPaste)
			parent->actionPaste(button_add, "", parent->current.children().size_s() + 1);
		else
			parent->newRequest((NewType)a->data().toInt());
	});
}


PIValueTreeEdit::GridWidgets::~GridWidgets() {
	delete button_add;
}


int PIValueTreeEdit::GridWidgets::getRow(QWidget * w) const {
	if (!w) return -1;
	for (int r = 0; r < lay->rowCount(); ++r) {
		for (int c = 0; c < lay->columnCount(); ++c) {
			auto * li = lay->itemAtPosition(r, c);
			if (!li) continue;
			if (li->widget() && (li->widget() == w)) return r;
		}
	}
	return -1;
}


void PIValueTreeEdit::GridWidgets::add(const PIValueTree & vt,
									   const PIString & label,
									   QWidget * w,
									   const PIString & comment,
									   bool is_group) {
	int col  = beginRow(vt, is_group);
	auto * l = new QLabel();
	auto * c = new QLabel(PIVariantEditorBase::vtTr(comment));
	l->setProperty(property_name, PI2QString(label));
	QString nn = PIVariantEditorBase::vtTr(label);
	if (!nn.isEmpty()) nn += ':';
	l->setText(nn);
	l->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
	c->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
	w->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
	lay->addWidget(l, row_count, col++, Qt::AlignVCenter | Qt::AlignRight);
	lay->addWidget(w, row_count, col++);
	lay->addWidget(c, row_count, col++, Qt::AlignVCenter | Qt::AlignLeft);
	widgets << l << w << c;
	labels << l;
	parent->comm_labels[vt.name()] = c;
	++row_count;
	changed();
}


void PIValueTreeEdit::GridWidgets::add(const PIValueTree & vt, QWidget * w, bool is_group) {
	int col = beginRow(vt, is_group);
	lay->addWidget(w, row_count, col, 1, -1);
	widgets << w;
	++row_count;
	changed();
}


void PIValueTreeEdit::GridWidgets::addRow(QWidget * w) {
	lay->addWidget(w, row_count, 0, 1, -1);
	widgets << w;
	++row_count;
	changed();
}


QToolButton * PIValueTreeEdit::GridWidgets::createConfigButton(const PIValueTree & vt, bool is_group) {
	auto * b = new ToolButton();
	b->setIcon(icon_conf);
	b->setPopupMode(QToolButton::InstantPopup);
	b->setMenu((is_group && !vt.isArray()) ? &menu_group : &menu_conf);
	b->setProperty(property_name, PI2QString(vt.name()));
	b->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
	b->setPressHandler([this]() { parent->checkActions(); });
	// connect(b, &QToolButton::pressed, parent, &PIValueTreeEdit::checkActions);
	connect(b, &QToolButton::triggered, this, [this, b](QAction * a) {
		parent->actionTriggered(b, Q2PIString(b->property(property_name).toString()), a);
	});
	return b;
}


void PIValueTreeEdit::GridWidgets::replace(int row, QWidget * w) {
	int col = removeRowEdits(row);
	lay->addWidget(w, row, col, 1, -1);
	widgets << w;
}


void PIValueTreeEdit::GridWidgets::replace(int row, const PIString & label, QWidget * w, const PIString & comment) {
	int col  = removeRowEdits(row);
	auto * l = new QLabel();
	auto * c = new QLabel(PIVariantEditorBase::vtTr(comment));
	l->setProperty(property_name, PI2QString(label));
	QString nn = PIVariantEditorBase::vtTr(label);
	if (!nn.isEmpty()) nn += ':';
	l->setText(nn);
	l->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
	c->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
	w->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
	lay->addWidget(l, row, col++, Qt::AlignVCenter | Qt::AlignRight);
	lay->addWidget(w, row, col++);
	lay->addWidget(c, row, col++, Qt::AlignVCenter | Qt::AlignLeft);
	widgets << l << w << c;
	labels << l;
	parent->comm_labels[label] = c;
}


int PIValueTreeEdit::GridWidgets::beginRow(const PIValueTree & vt, bool is_group) {
	if (!create_edit_buttons) return 0;
	auto * b = createConfigButton(vt, is_group);
	lay->addWidget(b, row_count, 0);
	widgets << b;
	return 1;
}


void PIValueTreeEdit::GridWidgets::rename(const PIString & prev_name, const PIString & new_name) {
	for (auto * w: widgets) {
		auto * gb = qobject_cast<QGroupBox *>(w);
		if (!gb) continue;
		if (gb->property(property_name).toString() == PI2QString(prev_name)) {
			gb->setProperty(property_name, PI2QString(new_name));
			gb->setTitle(PIVariantEditorBase::vtTr(new_name));
			break;
		}
	}
	for (auto * l: labels)
		if (l->property(property_name).toString() == PI2QString(prev_name)) {
			l->setProperty(property_name, PI2QString(new_name));
			QString nn = PIVariantEditorBase::vtTr(new_name);
			if (!nn.isEmpty()) nn += ':';
			l->setText(nn);
			break;
		}
}


void PIValueTreeEdit::GridWidgets::clear() {
	piDeleteAllAndClear(widgets);
	labels.clear();
	if (lay) delete lay;
	lay = new QGridLayout();
	lay->setContentsMargins(0, 0, 0, 0);
	setLayout(lay);
	row_count = 0;
	changed();
}


void PIValueTreeEdit::GridWidgets::changed() {
	if (!create_edit_buttons || !lay) return;
	lay->addWidget(button_add, row_count, 0);
	button_add->show();
	adjustSize();
}


void PIValueTreeEdit::GridWidgets::retranslate() {
	for (auto * w: widgets) {
		auto * gb = qobject_cast<QGroupBox *>(w);
		if (!gb) continue;
		gb->setTitle(PIVariantEditorBase::vtTr(Q2PIString(gb->property(property_name).toString())));
	}
	for (auto * l: labels) {
		QString nn = PIVariantEditorBase::vtTr(Q2PIString(l->property(property_name).toString()));
		if (!nn.isEmpty()) nn += ':';
		l->setText(nn);
	}
}


int PIValueTreeEdit::GridWidgets::removeRowEdits(int row) {
	int col = create_edit_buttons ? 1 : 0;
	for (int c = col; c < lay->columnCount(); ++c) {
		auto * li = lay->itemAtPosition(row, c);
		if (li) {
			QWidget * w = li->widget();
			if (w) {
				widgets.removeOne(w);
				QLabel * lbl = qobject_cast<QLabel *>(w);
				if (lbl) labels.removeOne(lbl);
				delete w;
			}
		}
	}
	return col;
}


void PIValueTreeEdit::GridWidgets::removeRow(int index) {
	if (!lay) return;
	if ((index < 0) || (index >= row_count) || (index >= lay->rowCount())) return;
	for (int c = 0; c < lay->columnCount(); ++c) {
		auto * li = lay->itemAtPosition(index, c);
		if (li) {
			QWidget * w = li->widget();
			if (w) {
				widgets.removeOne(w);
				QLabel * lbl = qobject_cast<QLabel *>(w);
				if (lbl) labels.removeOne(lbl);
				delete w;
			}
		}
	}
	--row_count;
	simplify();
	changed();
}


void PIValueTreeEdit::GridWidgets::simplify(const PIMap<int, int> & map) {
	struct Info {
		Qt::Alignment align;
		int row_span = 0;
		int col_span = 0;
	};
	if (!lay) return;
	QVector<QMap<int, QWidget *>> wg;
	QMap<QWidget *, Info> wa;
	for (int r = 0; r < lay->rowCount(); ++r) {
		QMap<int, QWidget *> row;
		for (int c = 0; c < lay->columnCount(); ++c) {
			auto * li = lay->itemAtPosition(r, c);
			if (!li) continue;
			if (li->widget()) {
				row[c] = li->widget();
				Info info;
				info.align = li->alignment();
				int pos[4];
				lay->getItemPosition(lay->indexOf(li->widget()), &(pos[0]), &(pos[1]), &(pos[2]), &(pos[3]));
				info.row_span    = pos[2];
				info.col_span    = pos[3];
				wa[li->widget()] = info;
				c += (pos[3] - 1);
			}
		}
		if (!row.isEmpty()) wg << row;
	}
	delete lay;
	lay = new QGridLayout();
	lay->setContentsMargins(0, 0, 0, 0);
	int rindex = 0;
	for (int i = 0; i < wg.size(); ++i) {
		int mi = map.value(i, i);
		if (mi < 0 || mi >= wg.size()) continue;
		QMapIterator<int, QWidget *> it(wg[mi]);
		while (it.hasNext()) {
			it.next();
			Info info = wa.value(it.value());
			lay->addWidget(it.value(), rindex, it.key(), info.row_span, info.col_span, info.align);
		}
		++rindex;
	}
	setLayout(lay);
	adjustSize();
}
