#include "pivaluetree_edit.h" #include "piqt.h" #include "pivaluetree_edit_parameters.h" #include "pivaluetree_edit_reorder.h" #include "pivariant_edit.h" #include "ui_pivaluetree_edit_array.h" #include #include #include #include #include #include using Attribute = PIValueTree::Attribute; const char property_name[] = "__name__"; 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) { source = v; build(); } PIValueTree PIValueTreeEdit::value() const { applyValues(); return source; } void PIValueTreeEdit::setGroupingEnabled(bool yes) { is_grouping = yes; build(); } void PIValueTreeEdit::setFullEditMode(bool yes) { is_full_edit = yes; build(); } void PIValueTreeEdit::rollback() { build(); } void PIValueTreeEdit::clear() { source = PIValueTree(); removeAll(); } void PIValueTreeEdit::changeEvent(QEvent * e) { if (e->type() == QEvent::LanguageChange) { if (widget_array) ui_array->retranslateUi(widget_array); } QWidget::changeEvent(e); } void PIValueTreeEdit::removeAll() { array_edits.clear(); value_edits.clear(); tree_edits.clear(); comm_labels.clear(); label_labels.clear(); 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; if (source.isArray()) { widget_array = new QWidget(); ui_array->setupUi(widget_array); applyArrayAttributes(); ui_array->layoutArray->addWidget(grid); grid->button_add->hide(); grid->show(); uint array_type = PIVariant::typeIDFromName(source.attribute(Attribute::arrayType).toString()); int index = 0; for (const auto & i: source.children()) { auto * ve = new PIVariantEdit(); ve->setAttributes(source.attributes()); ve->setValue(i.value(), array_type); grid->add(PIValueTree(), QString::number(++index), ve, PI2QString(i.comment())); array_edits << ve; } connect(ui_array->spinCount, QOverload::of(&QSpinBox::valueChanged), widget_array, [this, array_type](int value) { value = piMaxi(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(source.attributes()); ve->setValue(PIVariant::fromType(array_type), array_type); grid->add(PIValueTree(), QString::number(i + 1), ve, ""); array_edits << ve; } }); layout()->addWidget(widget_array); } else { grid->create_edit_buttons = is_full_edit; layout()->addWidget(grid); if (!source.hasChildren()) grid->clear(); for (const auto & i: source.children()) { if (i.attribute(Attribute::hidden, false).toBool()) 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 (source.isArray()) { if (array_edits.isNotEmpty()) source.mergeAttributes(array_edits[0]->attributes()); source.clearChildren(); for (int i = 0; i < array_edits.size_s(); ++i) source.addChild({PIString::fromNumber(i), array_edits[i]->value()}); } else { auto vit = value_edits.makeIterator(); while (vit.next()) { auto & c(source.child(vit.key())); c.mergeAttributes(vit.value()->attributes()); c.setValue(vit.value()->value()); } auto tit = tree_edits.makeIterator(); while (tit.next()) { auto & c(source.child(tit.key())); if (!c.isNull()) c = tit.value()->value(); } } } void PIValueTreeEdit::actionTriggered(QToolButton * button, const PIString & vn, QAction * a) { if (a == widget_params->actionRename) { 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: source.children()) { if (c.name() == nn) { QMessageBox::critical(nullptr, tr("Rename"), tr("This name already exists!")); return; } } source[vn].setName(nn); button->setProperty(property_name, PI2QString(nn)); grid->rename(PI2QString(vn), PI2QString(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(PI2QString(nn)); label_labels.remove(vn); } } if (a == widget_params->actionRemove) { source.remove(vn); grid->removeRow(grid->getRow(button)); value_edits.remove(vn); tree_edits.remove(vn); comm_labels.remove(vn); label_labels.remove(vn); } if (a == widget_params->actionChange) { auto & vt(source[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->applyArrayAttributes(); } else { bool was_label = vt.attribute(Attribute::isLabel, false).toBool(); auto * ve = value_edits.value(vn, nullptr); if (ve) vt.setValue(ve->value()); 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), PI2QString(vt.name()), ve, PI2QString(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(PI2QString(vt.comment())); } if (a == widget_params->actionReorder) { if (!widget_reorder->showFor(source)) return; grid->reorder(widget_reorder->map); auto cl = source.children(); source.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; source.addChild(cl[mi]); } } } PIValueTreeEdit * PIValueTreeEdit::addTreeEdit(const PIValueTree & vt) { auto * ve = new PIValueTreeEdit(); PIStringList rp = root_path; rp << vt.name(); ve->root_path = rp; ve->setGroupingEnabled(is_grouping); ve->setFullEditMode(is_full_edit); ve->setValue(vt); if (is_grouping) { auto * gb = new QGroupBox(); gb->setLayout(new QBoxLayout(QBoxLayout::TopToBottom)); gb->layout()->addWidget(ve); gb->setTitle(PI2QString(vt.name())); gb->setToolTip(PI2QString(vt.comment())); gb->setCheckable(true); gb->setChecked(true); gb->setAlignment(Qt::AlignCenter); connect(gb, &QGroupBox::toggled, ve, &QWidget::setVisible); grid->add(vt, gb, true); } else { grid->add(vt, PI2QString(vt.name()), ve, PI2QString(vt.comment()), true); } tree_edits[vt.name()] = ve; return ve; } void PIValueTreeEdit::addValueEdit(const PIValueTree & vt) { auto * ve = new PIVariantEdit(); applyVariantEdit(ve, vt); grid->add(vt, PI2QString(vt.name()), ve, PI2QString(vt.comment())); value_edits[vt.name()] = ve; } void PIValueTreeEdit::applyArrayAttributes() { ui_array->spinCount->setRange(source.attribute(Attribute::arrayMinCount, 0).toInt(), source.attribute(Attribute::arrayMaxCount, 65536).toInt()); ui_array->spinCount->setValue(source.children().size_s()); ui_array->widgetEdit->setVisible(source.attribute(Attribute::arrayResize, false).toBool()); uint array_type = PIVariant::typeIDFromName(source.attribute(Attribute::arrayType).toString()); for (int i = 0; i < array_edits.size_s(); ++i) { auto * w = array_edits[i]; w->setAttributes(source.attributes()); w->setValue(i < source.children().size_s() ? source.children()[i].value() : PIVariant(), array_type); } } QLabel * PIValueTreeEdit::newLabel(const PIValueTree & vt) { auto * l = new QLabel(); l->setAlignment(Qt::AlignCenter); l->setText(PI2QString(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(vt.attributes()); ve->setValue(vt.value()); ve->setFullEditMode(is_full_edit); } void PIValueTreeEdit::newRequest(NewType type) { PIString nn = Q2PIString(QInputDialog::getText(nullptr, tr("New item"), tr("Input new name:"))); if (nn.isEmpty()) return; for (const auto & c: source.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 (!widget_params->showFor(vt)) return; } if (type == NewType::Array) { vt.setAttribute(Attribute::arrayType, PIVariant::typeName()); if (!widget_params->showFor(vt)) return; } source.addChild(vt); switch (type) { case NewType::Value: addValueEdit(vt); break; case NewType::Group: addTreeEdit(vt); break; case NewType::Array: addTreeEdit(vt); 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; }; menu_group.addActions( {p->widget_params->actionRename, p->widget_params->actionReorder, newSeparator(), p->widget_params->actionRemove}); menu_conf.addActions({p->widget_params->actionRename, p->widget_params->actionChange, p->widget_params->actionReorder, newSeparator(), p->widget_params->actionRemove}); menu_new.addActions({p->widget_params->actionValue, p->widget_params->actionGroup, p->widget_params->actionArray}); button_add = new QToolButton(); button_add->setIcon(QIcon(":/icons/list-add.png")); button_add->setPopupMode(QToolButton::InstantPopup); button_add->setMenu(&menu_new); p->widget_params->actionValue->setData((int)NewType::Value); p->widget_params->actionGroup->setData((int)NewType::Group); p->widget_params->actionArray->setData((int)NewType::Array); connect(button_add, &QToolButton::triggered, this, [this](QAction * a) { 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, QString label, QWidget * w, const QString & comment, bool is_group) { int col = beginRow(vt, is_group); if (!label.isEmpty()) label += ':'; auto * l = new QLabel(label); auto * c = new QLabel(comment); 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::replace(int row, QWidget * w) { int col = removeRowEdits(row); lay->addWidget(w, row, col, 1, -1); widgets << w; } void PIValueTreeEdit::GridWidgets::replace(int row, QString label, QWidget * w, const QString & comment) { int col = removeRowEdits(row); if (!label.isEmpty()) label += ':'; auto * l = new QLabel(label); auto * c = new QLabel(comment); 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; } int PIValueTreeEdit::GridWidgets::beginRow(const PIValueTree & vt, bool is_group) { if (!create_edit_buttons) return 0; auto * b = new QToolButton(); 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); lay->addWidget(b, row_count, 0); connect(b, &QToolButton::triggered, this, [this, b](QAction * a) { parent->actionTriggered(b, Q2PIString(b->property(property_name).toString()), a); }); widgets << b; return 1; } void PIValueTreeEdit::GridWidgets::rename(QString prev_name, QString new_name) { if (parent->is_grouping) { for (auto * w: widgets) { auto * gb = qobject_cast(w); if (!gb) continue; if (gb->title() == prev_name) { gb->setTitle(new_name); break; } } } else { if (!prev_name.isEmpty()) prev_name += ':'; if (!new_name.isEmpty()) new_name += ':'; for (auto * l: labels) if (l->text() == prev_name) { l->setText(new_name); 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(); } 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(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(w); if (lbl) labels.removeOne(lbl); delete w; } } } --row_count; simplify(); changed(); } void PIValueTreeEdit::GridWidgets::simplify(const PIMap & map) { struct Info { Qt::Alignment align; int row_span = 1; int col_span = 1; }; if (!lay) return; QVector> wg; QMap wa; for (int r = 0; r < lay->rowCount(); ++r) { QMap 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), &(pos[0]), &(pos[1]), &(pos[2]), &(pos[3])); info.row_span = pos[2]; info.col_span = pos[3]; wa[li->widget()] = info; } } 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 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(); }