#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 #include using Attribute = PIValueTree::Attribute; const char property_name[] = "__name__"; 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 = real_grouping = g; if (parent_tree && g == Parent) real_grouping = parent_tree->real_grouping; if (real_grouping == Parent) real_grouping = Indent; 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::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(current.attributes()); ve->setValue(i.value(), array_type); grid->add(PIValueTree(), PIString::fromNumber(++index), ve, 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(current.attributes()); ve->setValue(PIVariant::fromType(array_type), array_type); grid->add(PIValueTree(), PIString::fromNumber(i + 1), ve, ""); array_edits << ve; } }); 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) current.addChild({PIString::fromNumber(i), array_edits[i]->value()}); } 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::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: 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; } } } return; } if (a == widget_params->actionRemove) { 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); return; } if (a == widget_params->actionChange) { 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->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())); return; } if (a == widget_params->actionReorder) { 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]); } return; } setGrouping((Grouping)a->data().toInt()); } 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->setGrouping((Grouping)vt.attribute(Attribute::grouping, Parent).toEnum().selectedValue()); ve->setFullEditMode(is_full_edit); ve->setValue(vt); switch (real_grouping) { case Indent: grid->add(vt, vt.name(), ve, vt.comment(), true); break; case Groups: { auto * gb = new GroupBox(ve); gb->setTitle(PI2QString(vt.name())); gb->setToolTip(PI2QString(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->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; } 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(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(vt.attributes()); ve->setValue(vt.value()); ve->setFullEditMode(is_full_edit); } void PIValueTreeEdit::createTabWidget() { if (tab_widget) return; tab_widget = new QTabWidget(); grid->addRow(tab_widget); } 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: 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 (!widget_params->showFor(vt)) return; } if (type == NewType::Array) { vt.setAttribute(Attribute::arrayType, PIVariant::typeName()); if (!widget_params->showFor(vt)) return; } current.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, p->widget_params->menu_grouping.menuAction(), 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, 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 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); 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(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(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(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 = 0; int col_span = 0; }; 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; 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 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(); }