mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-10-19 12:34:15 +02:00
Merge pull request #1534 from ispyisail/feat/multi-symbol-info
Add editing the information of several symbols at once (#1439)
This commit is contained in:
@@ -829,6 +829,8 @@ set(QET_SRC_FILES
|
||||
${QET_DIR}/sources/ui/elementinfopartwidget.h
|
||||
${QET_DIR}/sources/ui/elementinfowidget.cpp
|
||||
${QET_DIR}/sources/ui/elementinfowidget.h
|
||||
${QET_DIR}/sources/ui/multielementinfowidget.cpp
|
||||
${QET_DIR}/sources/ui/multielementinfowidget.h
|
||||
${QET_DIR}/sources/ui/terminalnumberingdialog.cpp
|
||||
${QET_DIR}/sources/ui/terminalnumberingdialog.h
|
||||
${QET_DIR}/sources/ui/cablenumberingdialog.cpp
|
||||
|
||||
+28
-2
@@ -39,7 +39,9 @@
|
||||
#include "qeticons.h"
|
||||
#include "qetpalette.h"
|
||||
#include "titleblock/integrationmovetemplateshandler.h"
|
||||
#include "PropertiesEditor/propertieseditordialog.h"
|
||||
#include "ui/diagrampropertiesdialog.h"
|
||||
#include "ui/multielementinfowidget.h"
|
||||
#include "ui/multipastedialog.h"
|
||||
#include "undocommand/changetitleblockcommand.h"
|
||||
#include "utils/conductorcreator.h"
|
||||
@@ -2080,9 +2082,33 @@ bool DiagramView::selectedItemHasFocus()
|
||||
*/
|
||||
void DiagramView::editSelection()
|
||||
{
|
||||
if (m_diagram -> isReadOnly() || m_diagram -> selectedItems().size() != 1 ) return;
|
||||
if (m_diagram -> isReadOnly()) return;
|
||||
|
||||
QGraphicsItem *item = m_diagram->selectedItems().first();
|
||||
//Several elements and nothing else: edit their information together (#1439)
|
||||
const QList<QGraphicsItem *> selection =
|
||||
MultiElementInfoWidget::withoutOwnTexts(m_diagram -> selectedItems());
|
||||
if (selection.size() > 1)
|
||||
{
|
||||
bool only_elements = true;
|
||||
for (QGraphicsItem *qgi : selection) {
|
||||
if (qgi->type() != Element::Type) {
|
||||
only_elements = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
const QList<Element *> elements = MultiElementInfoWidget::informationElements(selection);
|
||||
if (only_elements && elements.size() > 1)
|
||||
{
|
||||
PropertiesEditorDialog dialog(
|
||||
new MultiElementInfoWidget(elements, selection.size() - elements.size()),
|
||||
this);
|
||||
dialog.exec();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (selection.size() != 1) return;
|
||||
|
||||
QGraphicsItem *item = selection.first();
|
||||
|
||||
//We use dynamic_cast instead of qgraphicsitem_cast for QetGraphicsItem
|
||||
//because they haven't got their own type().
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
#include "../ui/conductorpropertieseditorwidget.h"
|
||||
#include "../ui/imagepropertieswidget.h"
|
||||
#include "../ui/inditextpropertieswidget.h"
|
||||
#include "../ui/multielementinfowidget.h"
|
||||
#include "../ui/shapegraphicsitempropertieswidget.h"
|
||||
|
||||
#include <QGraphicsItem>
|
||||
@@ -115,7 +116,7 @@ PropertiesEditorWidget *PropertiesEditorFactory::propertiesEditor(
|
||||
mapped << qgi;
|
||||
}
|
||||
}
|
||||
items = mapped;
|
||||
items = MultiElementInfoWidget::withoutOwnTexts(mapped);
|
||||
|
||||
const int count_ = items.size();
|
||||
if (count_ == 0) {
|
||||
@@ -124,19 +125,37 @@ PropertiesEditorWidget *PropertiesEditorFactory::propertiesEditor(
|
||||
QGraphicsItem *item = items.first();
|
||||
const int type_ = item->type();
|
||||
|
||||
//The editor widget can only edit one item
|
||||
//or several items of the same type
|
||||
for (auto qgi : items) {
|
||||
if (qgi->type() != type_) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
QString class_name;
|
||||
if (editor) {
|
||||
class_name = editor->metaObject()->className();
|
||||
}
|
||||
|
||||
//The editor widget can only edit one item
|
||||
//or several items of the same type,
|
||||
//or the information of several elements (#1439)
|
||||
bool same_type = true;
|
||||
for (auto qgi : items) {
|
||||
if (qgi->type() != type_) {
|
||||
same_type = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!same_type || (type_ == Element::Type && count_ > 1))
|
||||
{
|
||||
const QList<Element *> elements =
|
||||
MultiElementInfoWidget::informationElements(items);
|
||||
if (elements.isEmpty()) {
|
||||
return nullptr;
|
||||
}
|
||||
const int other_items = count_ - elements.size();
|
||||
if (class_name == MultiElementInfoWidget::staticMetaObject.className())
|
||||
{
|
||||
static_cast<MultiElementInfoWidget *>(editor)->setElements(elements, other_items);
|
||||
return editor;
|
||||
}
|
||||
return new MultiElementInfoWidget(elements, other_items, parent);
|
||||
}
|
||||
|
||||
switch (type_)
|
||||
{
|
||||
case Conductor::Type: //1001
|
||||
@@ -162,9 +181,6 @@ PropertiesEditorWidget *PropertiesEditorFactory::propertiesEditor(
|
||||
}
|
||||
case Element::Type: //1000
|
||||
{
|
||||
if (count_ > 1) {
|
||||
return nullptr;
|
||||
}
|
||||
auto elmt = static_cast<Element*>(item);
|
||||
//auto created_editor = new ElementPropertiesWidget(elmt, parent);
|
||||
|
||||
|
||||
@@ -64,6 +64,7 @@
|
||||
#include "factory/qetgraphicstablefactory.h"
|
||||
#include "print/projectprintwindow.h"
|
||||
#include "project/projectpropertieshandler.h"
|
||||
#include "ui/multielementinfowidget.h"
|
||||
#include "ui/projectpropertiesdialog.h"
|
||||
#include "projectview.h"
|
||||
#include "qetproject.h"
|
||||
@@ -3085,6 +3086,21 @@ void QETDiagramEditor::slot_updateUndoStack()
|
||||
undo_group.setActiveStack(currentProjectView()->project()->undoStack());
|
||||
}
|
||||
|
||||
namespace {
|
||||
/**
|
||||
@return the elements of @p diagram's selection which take information
|
||||
*/
|
||||
QList<Element *> selectedInformationElements(Diagram *diagram)
|
||||
{
|
||||
QList<Element *> list;
|
||||
for (Element *element : DiagramContent(diagram).m_elements) {
|
||||
if (MultiElementInfoWidget::takesInformations(element))
|
||||
list << element;
|
||||
}
|
||||
return list;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
/**
|
||||
@brief QETDiagramEditor::slot_updateComplexActions
|
||||
Manage the actions that need some conditions to be enabled or not.
|
||||
@@ -3211,7 +3227,16 @@ void QETDiagramEditor::slot_updateComplexActions()
|
||||
+ selected_conductors_count
|
||||
+ selected_cables;
|
||||
|
||||
if (selected_editable == 1)
|
||||
//Several elements and nothing else: edit their information together (#1439)
|
||||
if (!ro && selected_editable > 1 && selected_editable == selected_elements_count
|
||||
&& selectedInformationElements(diagram_).size() > 1)
|
||||
{
|
||||
m_edit_selection -> setEnabled(true);
|
||||
m_edit_selection -> setText(tr("Edit the information of the elements",
|
||||
"edit several elements"));
|
||||
m_edit_selection -> setIcon(QET::Icons::ElementEdit);
|
||||
}
|
||||
else if (selected_editable == 1)
|
||||
{
|
||||
m_edit_selection -> setEnabled(true);
|
||||
//edit element
|
||||
@@ -3323,39 +3348,6 @@ void QETDiagramEditor::slot_updateModeActions()
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
/**
|
||||
@return true if @p element has information the user can edit, the
|
||||
same element kinds ElementPropertiesWidget gives an ElementInfoWidget.
|
||||
Plain slaves and folio reports take theirs from what they are linked to.
|
||||
*/
|
||||
bool takesElementInformations(const Element *element)
|
||||
{
|
||||
switch (element->linkType()) {
|
||||
case Element::Simple:
|
||||
case Element::Thumbnail:
|
||||
case Element::Master:
|
||||
case Element::Terminale:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@return the elements of @p diagram's selection which take information
|
||||
*/
|
||||
QList<Element *> selectedInformationElements(Diagram *diagram)
|
||||
{
|
||||
QList<Element *> list;
|
||||
for (Element *element : DiagramContent(diagram).m_elements) {
|
||||
if (takesElementInformations(element))
|
||||
list << element;
|
||||
}
|
||||
return list;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
/**
|
||||
@brief QETDiagramEditor::slot_updatePasteAction
|
||||
Gere les actions ayant besoin du presse-papier
|
||||
|
||||
@@ -111,6 +111,18 @@ void ElementInfoWidget::apply()
|
||||
m_element -> diagram() -> undoStack().push(undo);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief ElementInfoWidget::reset
|
||||
Forget the edits not applied yet: show again the information
|
||||
the element has.
|
||||
*/
|
||||
void ElementInfoWidget::reset()
|
||||
{
|
||||
if (m_element) {
|
||||
updateUi();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief ElementInfoWidget::associatedUndo
|
||||
If the edited info is different of the actual element info,
|
||||
|
||||
@@ -53,6 +53,7 @@ class ElementInfoWidget : public AbstractElementPropertiesEditorWidget
|
||||
|
||||
void setElement(Element *element) override;
|
||||
void apply() override;
|
||||
void reset() override;
|
||||
QUndoCommand *associatedUndo () const override;
|
||||
QString title() const override {return tr("Information");}
|
||||
bool setLiveEdit(bool live_edit) override;
|
||||
|
||||
@@ -36,6 +36,11 @@
|
||||
#include <QUndoStack>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
namespace {
|
||||
//The tab last picked in the selection properties panel
|
||||
QString s_live_tab;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief ElementPropertiesWidget::ElementPropertiesWidget
|
||||
Default constructor
|
||||
@@ -149,6 +154,7 @@ void ElementPropertiesWidget::setDynamicText(DynamicElementTextItem *text)
|
||||
{
|
||||
DynamicElementTextItemEditor *detie = static_cast<DynamicElementTextItemEditor *>(aepew);
|
||||
m_tab->setCurrentWidget(detie);
|
||||
m_text_tab_wanted = true;
|
||||
detie->setCurrentText(text);
|
||||
}
|
||||
}
|
||||
@@ -175,6 +181,7 @@ void ElementPropertiesWidget::setTextsGroup(ElementTextItemGroup *group)
|
||||
{
|
||||
DynamicElementTextItemEditor *detie = static_cast<DynamicElementTextItemEditor *>(aepew);
|
||||
m_tab->setCurrentWidget(detie);
|
||||
m_text_tab_wanted = true;
|
||||
detie->setCurrentGroup(group);
|
||||
}
|
||||
}
|
||||
@@ -225,6 +232,18 @@ bool ElementPropertiesWidget::setLiveEdit(bool live_edit)
|
||||
foreach (AbstractElementPropertiesEditorWidget *aepew, m_list_editor)
|
||||
aepew->setLiveEdit(m_live_edit);
|
||||
|
||||
if (m_live_edit && !m_text_tab_wanted && !s_live_tab.isEmpty())
|
||||
{
|
||||
for (int i = 0 ; i < m_tab->count() ; ++i)
|
||||
{
|
||||
if (m_tab->tabText(i) == s_live_tab)
|
||||
{
|
||||
m_tab->setCurrentIndex(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -262,6 +281,14 @@ void ElementPropertiesWidget::editElement()
|
||||
void ElementPropertiesWidget::buildGui()
|
||||
{
|
||||
m_tab = new QTabWidget(this);
|
||||
//The selection properties panel builds a new editor each time
|
||||
//the selection goes from one element to something else and back:
|
||||
//it opens on the tab last picked there (#1439)
|
||||
connect(m_tab, &QTabWidget::tabBarClicked, this, [this](int index) {
|
||||
if (m_live_edit && index >= 0) {
|
||||
s_live_tab = m_tab->tabText(index);
|
||||
}
|
||||
});
|
||||
QVBoxLayout *main_layout = new QVBoxLayout(this);
|
||||
main_layout -> addWidget(m_tab);
|
||||
setLayout(main_layout);
|
||||
|
||||
@@ -61,6 +61,7 @@ class ElementPropertiesWidget : public AbstractElementPropertiesEditorWidget
|
||||
QTabWidget *m_tab;
|
||||
QList <AbstractElementPropertiesEditorWidget *> m_list_editor;
|
||||
QWidget *m_general_widget;
|
||||
bool m_text_tab_wanted = false;
|
||||
};
|
||||
|
||||
#endif // ELEMENTPROPERTIESWIDGET_H
|
||||
|
||||
@@ -0,0 +1,837 @@
|
||||
/*
|
||||
Copyright 2006-2026 The QElectroTech Team
|
||||
This file is part of QElectroTech.
|
||||
|
||||
QElectroTech 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.
|
||||
|
||||
QElectroTech 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 QElectroTech. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include "multielementinfowidget.h"
|
||||
|
||||
#include "../dataBase/projectdatabase.h"
|
||||
#include "../diagram.h"
|
||||
#include "../qet.h"
|
||||
#include "../qeticons.h"
|
||||
#include "../qetgraphicsitem/dynamicelementtextitem.h"
|
||||
#include "../qetgraphicsitem/element.h"
|
||||
#include "../qetgraphicsitem/elementtextitemgroup.h"
|
||||
#include "../qetinformation.h"
|
||||
#include "../qetproject.h"
|
||||
#include "../undocommand/changeelementinformationcommand.h"
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QCompleter>
|
||||
#include <QGridLayout>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QScrollArea>
|
||||
#include <QSet>
|
||||
#include <QSqlQuery>
|
||||
#include <QTimer>
|
||||
#include <QToolButton>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
namespace {
|
||||
/**
|
||||
The change of one editing session: the keystrokes applied one by one in
|
||||
the selection panel make one undo step, but never join a change made
|
||||
before, by the same editor or another one, to the same elements.
|
||||
*/
|
||||
class MultiElementInfoCommand : public ChangeElementInformationCommand
|
||||
{
|
||||
public:
|
||||
MultiElementInfoCommand(
|
||||
const QMap<QPointer<Element>, QPair<DiagramContext, DiagramContext>> &map,
|
||||
quint64 session) :
|
||||
ChangeElementInformationCommand(map),
|
||||
m_session(session)
|
||||
{}
|
||||
|
||||
int id() const override {return 1439;}
|
||||
bool mergeWith(const QUndoCommand *other) override
|
||||
{
|
||||
if (other->id() != id()
|
||||
|| static_cast<const MultiElementInfoCommand *>(other)->m_session != m_session) {
|
||||
return false;
|
||||
}
|
||||
return ChangeElementInformationCommand::mergeWith(other);
|
||||
}
|
||||
|
||||
private:
|
||||
quint64 m_session;
|
||||
};
|
||||
|
||||
quint64 s_last_session = 0;
|
||||
|
||||
const QString EXCLUDE_FROM_BOM = QStringLiteral("exclude_from_bom");
|
||||
const QString AUTO_NUM_LOCKED = QStringLiteral("auto_num_locked");
|
||||
const QString POTENTIAL_ISOLATING = QStringLiteral("potential_isolating");
|
||||
|
||||
QString flagText(bool on)
|
||||
{
|
||||
return on ? QStringLiteral("true") : QStringLiteral("false");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief MultiElementInfoWidget::MultiElementInfoWidget
|
||||
@param elements : the elements to edit
|
||||
@param other_items : how many other selected items this editor leaves
|
||||
alone, only shown to the user
|
||||
@param parent
|
||||
*/
|
||||
MultiElementInfoWidget::MultiElementInfoWidget(const QList<Element *> &elements,
|
||||
int other_items,
|
||||
QWidget *parent) :
|
||||
PropertiesEditorWidget(parent)
|
||||
{
|
||||
buildInterface();
|
||||
setElements(elements, other_items);
|
||||
}
|
||||
|
||||
MultiElementInfoWidget::~MultiElementInfoWidget()
|
||||
{
|
||||
qDeleteAll(m_rows);
|
||||
qDeleteAll(m_flags);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief MultiElementInfoWidget::takesInformations
|
||||
@return true if @p element has information the user can edit: the
|
||||
element kinds ElementPropertiesWidget gives an Information tab.
|
||||
Plain slaves and folio reports take theirs from what they are linked to.
|
||||
*/
|
||||
bool MultiElementInfoWidget::takesInformations(const Element *element)
|
||||
{
|
||||
if (!element) {
|
||||
return false;
|
||||
}
|
||||
switch (element->linkType()) {
|
||||
case Element::Simple:
|
||||
case Element::Thumbnail:
|
||||
case Element::Master:
|
||||
case Element::Terminale:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief MultiElementInfoWidget::informationElements
|
||||
@return the elements of @p items which take information
|
||||
*/
|
||||
QList<Element *> MultiElementInfoWidget::informationElements(const QList<QGraphicsItem *> &items)
|
||||
{
|
||||
QList<Element *> list;
|
||||
for (QGraphicsItem *qgi : items) {
|
||||
if (qgi->type() != Element::Type) {
|
||||
continue;
|
||||
}
|
||||
auto element = static_cast<Element *>(qgi);
|
||||
if (takesInformations(element)) {
|
||||
list << element;
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief MultiElementInfoWidget::withoutOwnTexts
|
||||
@return @p items without the texts and text groups of elements which
|
||||
are in @p items too: select all takes an element's texts with it, and
|
||||
they are part of the element rather than other items.
|
||||
*/
|
||||
QList<QGraphicsItem *> MultiElementInfoWidget::withoutOwnTexts(const QList<QGraphicsItem *> &items)
|
||||
{
|
||||
QSet<QGraphicsItem *> elements;
|
||||
for (QGraphicsItem *qgi : items) {
|
||||
if (qgi->type() == Element::Type) {
|
||||
elements.insert(qgi);
|
||||
}
|
||||
}
|
||||
if (elements.isEmpty()) {
|
||||
return items;
|
||||
}
|
||||
|
||||
QList<QGraphicsItem *> list;
|
||||
for (QGraphicsItem *qgi : items)
|
||||
{
|
||||
Element *owner = nullptr;
|
||||
if (qgi->type() == DynamicElementTextItem::Type) {
|
||||
owner = static_cast<DynamicElementTextItem *>(qgi)->parentElement();
|
||||
} else if (auto group = dynamic_cast<ElementTextItemGroup *>(qgi)) {
|
||||
owner = group->parentElement();
|
||||
}
|
||||
if (!owner || !elements.contains(owner)) {
|
||||
list << qgi;
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief MultiElementInfoWidget::setElements
|
||||
Edit @p elements, forgetting any pending change.
|
||||
*/
|
||||
void MultiElementInfoWidget::setElements(const QList<Element *> &elements, int other_items)
|
||||
{
|
||||
for (const auto &element : std::as_const(m_elements)) {
|
||||
if (element) {
|
||||
disconnect(element.data(), &Element::elementInfoChange,
|
||||
this, &MultiElementInfoWidget::elementInfoChanged);
|
||||
}
|
||||
}
|
||||
|
||||
m_elements.clear();
|
||||
for (Element *element : elements) {
|
||||
m_elements << QPointer<Element>(element);
|
||||
connect(element, &Element::elementInfoChange,
|
||||
this, &MultiElementInfoWidget::elementInfoChanged);
|
||||
}
|
||||
m_other_items = other_items;
|
||||
|
||||
updateUi();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief MultiElementInfoWidget::elements
|
||||
@return the edited elements still alive
|
||||
*/
|
||||
QList<Element *> MultiElementInfoWidget::elements() const
|
||||
{
|
||||
QList<Element *> list;
|
||||
for (const auto &element : m_elements) {
|
||||
if (element) {
|
||||
list << element.data();
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief MultiElementInfoWidget::apply
|
||||
Push the change of every element, as one undo step.
|
||||
*/
|
||||
void MultiElementInfoWidget::apply()
|
||||
{
|
||||
QUndoCommand *undo = associatedUndo();
|
||||
if (!undo) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto list = elements();
|
||||
if (list.isEmpty() || !list.first()->diagram()) {
|
||||
delete undo;
|
||||
return;
|
||||
}
|
||||
|
||||
m_applying = true;
|
||||
list.first()->diagram()->undoStack().push(undo);
|
||||
m_applying = false;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief MultiElementInfoWidget::associatedUndo
|
||||
@return the command giving each element its new information, or
|
||||
nullptr if no element would change.
|
||||
A field left alone gives each element back the value it had when the
|
||||
editor was filled, so restoring a field in live edit undoes it.
|
||||
*/
|
||||
QUndoCommand *MultiElementInfoWidget::associatedUndo() const
|
||||
{
|
||||
QMap<QPointer<Element>, QPair<DiagramContext, DiagramContext>> changes;
|
||||
|
||||
for (Element *element : elements())
|
||||
{
|
||||
const DiagramContext old_info = element->elementInformations();
|
||||
const DiagramContext snap = m_snapshot.value(element, old_info);
|
||||
DiagramContext new_info = old_info;
|
||||
|
||||
for (const Row *row : m_rows)
|
||||
{
|
||||
QString txt;
|
||||
switch (row->state) {
|
||||
case Keep:
|
||||
if (snap.contains(row->key)) {
|
||||
new_info.addValue(row->key, snap.value(row->key),
|
||||
snap.keyMustShow(row->key));
|
||||
} else {
|
||||
new_info.remove(row->key);
|
||||
}
|
||||
continue;
|
||||
case Clear:
|
||||
new_info.remove(row->key);
|
||||
continue;
|
||||
case Set:
|
||||
if (!row->edit->hasAcceptableInput()) {
|
||||
continue;
|
||||
}
|
||||
txt = row->edit->text();
|
||||
txt.remove(QStringLiteral("\r"));
|
||||
txt.remove(QStringLiteral("\n"));
|
||||
if (txt.isEmpty()) {
|
||||
new_info.remove(row->key);
|
||||
} else {
|
||||
new_info.addValue(row->key, txt,
|
||||
old_info.contains(row->key)
|
||||
? old_info.keyMustShow(row->key)
|
||||
: true);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
//The element keeps following its numbering scheme as long as
|
||||
//its formula is left as it was; a formula given by hand is its own.
|
||||
if (new_info.value(QETInformation::ELMT_FORMULA)
|
||||
== snap.value(QETInformation::ELMT_FORMULA)
|
||||
&& snap.contains(QETInformation::ELMT_FORMULA_ID)) {
|
||||
new_info.addValue(QETInformation::ELMT_FORMULA_ID,
|
||||
snap.value(QETInformation::ELMT_FORMULA_ID), false);
|
||||
} else {
|
||||
new_info.remove(QETInformation::ELMT_FORMULA_ID);
|
||||
}
|
||||
|
||||
for (const Flag *flag : m_flags)
|
||||
{
|
||||
if (flag->check->isHidden()) {
|
||||
continue;
|
||||
}
|
||||
if (!flag->changed) {
|
||||
if (snap.contains(flag->key)) {
|
||||
new_info.addValue(flag->key, snap.value(flag->key),
|
||||
snap.keyMustShow(flag->key));
|
||||
} else {
|
||||
new_info.remove(flag->key);
|
||||
}
|
||||
} else {
|
||||
new_info.addValue(flag->key, flagText(flag->check->isChecked()));
|
||||
}
|
||||
}
|
||||
|
||||
if (new_info != old_info) {
|
||||
changes.insert(QPointer<Element>(element), qMakePair(old_info, new_info));
|
||||
}
|
||||
}
|
||||
|
||||
if (changes.isEmpty()) {
|
||||
return nullptr;
|
||||
}
|
||||
return new MultiElementInfoCommand(changes, m_session);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief MultiElementInfoWidget::reset
|
||||
Bring back every field to what the elements had, and in live edit
|
||||
give it back to the elements.
|
||||
*/
|
||||
void MultiElementInfoWidget::reset()
|
||||
{
|
||||
m_updating = true;
|
||||
for (Row *row : std::as_const(m_rows)) {
|
||||
restoreRow(row);
|
||||
}
|
||||
for (Flag *flag : std::as_const(m_flags)) {
|
||||
restoreFlag(flag);
|
||||
}
|
||||
m_updating = false;
|
||||
updateLabelRow();
|
||||
changed();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief MultiElementInfoWidget::updateUi
|
||||
Read the elements again, forgetting any pending change.
|
||||
*/
|
||||
void MultiElementInfoWidget::updateUi()
|
||||
{
|
||||
takeSnapshot();
|
||||
fillRows();
|
||||
updateHeader();
|
||||
updateSuggestions();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief MultiElementInfoWidget::fieldState
|
||||
@return what the field @p key does on apply
|
||||
*/
|
||||
MultiElementInfoWidget::FieldState MultiElementInfoWidget::fieldState(const QString &key) const
|
||||
{
|
||||
const Row *row = rowForKey(key);
|
||||
return row ? row->state : Keep;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief MultiElementInfoWidget::setFieldText
|
||||
Type @p text in the field @p key, as the user would.
|
||||
*/
|
||||
void MultiElementInfoWidget::setFieldText(const QString &key, const QString &text)
|
||||
{
|
||||
if (Row *row = rowForKey(key)) {
|
||||
row->edit->setText(text);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief MultiElementInfoWidget::setFieldCleared
|
||||
Tick or untick the erase box of the field @p key, as the user would.
|
||||
*/
|
||||
void MultiElementInfoWidget::setFieldCleared(const QString &key, bool cleared)
|
||||
{
|
||||
if (Row *row = rowForKey(key)) {
|
||||
row->erase->setChecked(cleared);
|
||||
rowErased(row);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief MultiElementInfoWidget::restoreField
|
||||
Click the restore button of the field @p key.
|
||||
*/
|
||||
void MultiElementInfoWidget::restoreField(const QString &key)
|
||||
{
|
||||
if (Row *row = rowForKey(key)) {
|
||||
restoreRow(row);
|
||||
changed();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief MultiElementInfoWidget::fieldIsMixed
|
||||
@return true if the elements had different values for @p key
|
||||
*/
|
||||
bool MultiElementInfoWidget::fieldIsMixed(const QString &key) const
|
||||
{
|
||||
const Row *row = rowForKey(key);
|
||||
return row && row->mixed;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief MultiElementInfoWidget::setLiveEdit
|
||||
@param live_edit : true to apply every change at once
|
||||
@return always true
|
||||
*/
|
||||
bool MultiElementInfoWidget::setLiveEdit(bool live_edit)
|
||||
{
|
||||
m_live_edit = live_edit;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief MultiElementInfoWidget::buildInterface
|
||||
One row per information key: name, text, erase box, restore button.
|
||||
The numbering scheme, label freezing and number picker of the
|
||||
one-element editor are left out: they are about one element.
|
||||
*/
|
||||
void MultiElementInfoWidget::buildInterface()
|
||||
{
|
||||
auto main_layout = new QVBoxLayout(this);
|
||||
main_layout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
m_header = new QLabel(this);
|
||||
m_header->setWordWrap(true);
|
||||
m_header->setContentsMargins(5, 5, 5, 0);
|
||||
main_layout->addWidget(m_header);
|
||||
|
||||
auto flags_layout = new QVBoxLayout();
|
||||
main_layout->addLayout(flags_layout);
|
||||
|
||||
const QList<QPair<QString, QString>> flags {
|
||||
{EXCLUDE_FROM_BOM, tr("Exclude from the bill of materials")},
|
||||
{POTENTIAL_ISOLATING, tr("Function separation")},
|
||||
{AUTO_NUM_LOCKED, tr("Exclude from automatic numbering")}
|
||||
};
|
||||
for (const auto &pair : flags)
|
||||
{
|
||||
auto flag = new Flag;
|
||||
flag->key = pair.first;
|
||||
flag->check = new QCheckBox(pair.second, this);
|
||||
flag->restore = new QToolButton(this);
|
||||
flag->restore->setIcon(QET::Icons::EditUndo);
|
||||
flag->restore->setAutoRaise(true);
|
||||
flag->restore->setToolTip(tr("Restore the values the elements had"));
|
||||
auto row_layout = new QHBoxLayout();
|
||||
row_layout->setContentsMargins(5, 0, 5, 0);
|
||||
row_layout->addWidget(flag->check, 1);
|
||||
row_layout->addWidget(flag->restore);
|
||||
flags_layout->addLayout(row_layout);
|
||||
m_flags << flag;
|
||||
|
||||
connect(flag->check, &QCheckBox::clicked, this, [this, flag]() {
|
||||
flagClicked(flag);
|
||||
});
|
||||
connect(flag->restore, &QToolButton::clicked, this, [this, flag]() {
|
||||
restoreFlag(flag);
|
||||
changed();
|
||||
});
|
||||
}
|
||||
|
||||
auto scroll = new QScrollArea(this);
|
||||
scroll->setWidgetResizable(true);
|
||||
scroll->setFrameShape(QFrame::NoFrame);
|
||||
auto content = new QWidget(scroll);
|
||||
auto grid = new QGridLayout(content);
|
||||
grid->setColumnStretch(1, 1);
|
||||
|
||||
QStringList keys = QETInformation::elementInfoKeys();
|
||||
//It has its own check box above, see ElementInfoWidget::buildInterface
|
||||
keys.removeAll(EXCLUDE_FROM_BOM);
|
||||
|
||||
int line = 0;
|
||||
for (const QString &key : std::as_const(keys))
|
||||
{
|
||||
auto row = new Row;
|
||||
row->key = key;
|
||||
row->label = new QLabel(QETInformation::translatedInfoKey(key), content);
|
||||
row->edit = new QLineEdit(content);
|
||||
row->edit->setClearButtonEnabled(true);
|
||||
row->erase = new QCheckBox(content);
|
||||
row->erase->setToolTip(tr("Delete this text"));
|
||||
row->restore = new QToolButton(content);
|
||||
row->restore->setIcon(QET::Icons::EditUndo);
|
||||
row->restore->setAutoRaise(true);
|
||||
row->restore->setToolTip(tr("Restore the values the elements had"));
|
||||
|
||||
if (key == QETInformation::ELMT_WIDTH
|
||||
|| key == QETInformation::ELMT_HEIGHT
|
||||
|| key == QETInformation::ELMT_DEPTH) {
|
||||
row->edit->setValidator(new QETInformation::NumericInfoValidator(row->edit));
|
||||
row->edit->setToolTip(tr("Decimal number with a dot as separator (e.g. 80.5)"));
|
||||
}
|
||||
|
||||
grid->addWidget(row->label, line, 0);
|
||||
grid->addWidget(row->edit, line, 1);
|
||||
grid->addWidget(row->erase, line, 2);
|
||||
grid->addWidget(row->restore, line, 3);
|
||||
++line;
|
||||
m_rows << row;
|
||||
|
||||
connect(row->edit, &QLineEdit::textChanged, this, [this, row]() {
|
||||
if (!m_updating) {
|
||||
rowEdited(row);
|
||||
}
|
||||
});
|
||||
connect(row->erase, &QCheckBox::clicked, this, [this, row]() {
|
||||
rowErased(row);
|
||||
});
|
||||
connect(row->restore, &QToolButton::clicked, this, [this, row]() {
|
||||
restoreRow(row);
|
||||
changed();
|
||||
});
|
||||
}
|
||||
grid->setRowStretch(line, 1);
|
||||
|
||||
scroll->setWidget(content);
|
||||
main_layout->addWidget(scroll, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief MultiElementInfoWidget::takeSnapshot
|
||||
Keep the information every element has now: the values a field left
|
||||
alone gives back.
|
||||
*/
|
||||
void MultiElementInfoWidget::takeSnapshot()
|
||||
{
|
||||
m_session = ++s_last_session;
|
||||
m_snapshot.clear();
|
||||
for (Element *element : elements()) {
|
||||
m_snapshot.insert(element, element->elementInformations());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief MultiElementInfoWidget::fillRows
|
||||
Show in each field the value shared by every element, or that the
|
||||
values differ.
|
||||
*/
|
||||
void MultiElementInfoWidget::fillRows()
|
||||
{
|
||||
const auto list = elements();
|
||||
m_updating = true;
|
||||
|
||||
for (Row *row : std::as_const(m_rows))
|
||||
{
|
||||
row->mixed = false;
|
||||
row->initial.clear();
|
||||
bool first = true;
|
||||
for (Element *element : list) {
|
||||
const QString value = m_snapshot.value(element).value(row->key).toString();
|
||||
if (first) {
|
||||
row->initial = value;
|
||||
first = false;
|
||||
} else if (value != row->initial) {
|
||||
row->mixed = true;
|
||||
row->initial.clear();
|
||||
break;
|
||||
}
|
||||
}
|
||||
restoreRow(row);
|
||||
}
|
||||
|
||||
bool all_terminals = !list.isEmpty();
|
||||
for (Element *element : list) {
|
||||
if (element->elementData().m_type != ElementData::Terminal) {
|
||||
all_terminals = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (Flag *flag : std::as_const(m_flags))
|
||||
{
|
||||
flag->check->setVisible(flag->key == EXCLUDE_FROM_BOM || all_terminals);
|
||||
flag->restore->setVisible(flag->check->isVisible());
|
||||
|
||||
int on = 0;
|
||||
for (Element *element : list) {
|
||||
if (QET::infoFlagIsTrue(m_snapshot.value(element).value(flag->key).toString())) {
|
||||
++on;
|
||||
}
|
||||
}
|
||||
flag->initial = on == 0 ? Qt::Unchecked
|
||||
: on == list.size() ? Qt::Checked
|
||||
: Qt::PartiallyChecked;
|
||||
restoreFlag(flag);
|
||||
}
|
||||
|
||||
m_updating = false;
|
||||
updateLabelRow();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief MultiElementInfoWidget::updateHeader
|
||||
Say how many elements are edited, and that the rest of the selection
|
||||
is not.
|
||||
*/
|
||||
void MultiElementInfoWidget::updateHeader()
|
||||
{
|
||||
QString text = tr("Elements edited: %1").arg(elements().size());
|
||||
if (m_other_items > 0) {
|
||||
text += QStringLiteral("\n")
|
||||
+ tr("Other selected items, left unchanged: %1").arg(m_other_items);
|
||||
}
|
||||
m_header->setText(text);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief MultiElementInfoWidget::updateSuggestions
|
||||
Offer, while typing, the values the project already uses,
|
||||
the way ElementInfoWidget::updateSuggestions does.
|
||||
*/
|
||||
void MultiElementInfoWidget::updateSuggestions()
|
||||
{
|
||||
const auto list = elements();
|
||||
Diagram *diagram = list.isEmpty() ? nullptr : list.first()->diagram();
|
||||
QETProject *project = diagram ? diagram->project() : nullptr;
|
||||
if (!project || !project->dataBase()) {
|
||||
return;
|
||||
}
|
||||
|
||||
//Only a column of element_info can be queried; the key becomes
|
||||
//part of the SQL text, so it is checked against that list.
|
||||
const QStringList columns = QETInformation::elementInfoKeys();
|
||||
for (Row *row : std::as_const(m_rows))
|
||||
{
|
||||
if (row->key == QETInformation::ELMT_LABEL || !columns.contains(row->key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
QStringList values;
|
||||
auto query = project->dataBase()->newQuery(QStringLiteral(
|
||||
"SELECT v, MAX(n) FROM ("
|
||||
"SELECT \"%1\" AS v, COUNT(*) AS n FROM element_info "
|
||||
"WHERE \"%1\" IS NOT NULL AND \"%1\" != '' "
|
||||
"GROUP BY \"%1\") "
|
||||
"GROUP BY v COLLATE NOCASE "
|
||||
"ORDER BY v COLLATE NOCASE").arg(row->key));
|
||||
while (query.next()) {
|
||||
values << query.value(0).toString();
|
||||
}
|
||||
|
||||
if (values.isEmpty()) {
|
||||
row->edit->setCompleter(nullptr);
|
||||
continue;
|
||||
}
|
||||
auto completer = new QCompleter(values, row->edit);
|
||||
completer->setCaseSensitivity(Qt::CaseInsensitive);
|
||||
completer->setFilterMode(Qt::MatchContains);
|
||||
row->edit->setCompleter(completer);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief MultiElementInfoWidget::rowEdited
|
||||
The user typed in a field: its text goes to every element, unless
|
||||
it is back to what they all had.
|
||||
*/
|
||||
void MultiElementInfoWidget::rowEdited(Row *row)
|
||||
{
|
||||
const QString text = row->edit->text();
|
||||
if (row->mixed ? text.isEmpty() : text == row->initial) {
|
||||
row->state = Keep;
|
||||
} else {
|
||||
row->state = Set;
|
||||
}
|
||||
updateRowLook(row);
|
||||
if (row->key == QETInformation::ELMT_FORMULA) {
|
||||
updateLabelRow();
|
||||
}
|
||||
changed();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief MultiElementInfoWidget::rowErased
|
||||
The erase box of a field was ticked or unticked.
|
||||
*/
|
||||
void MultiElementInfoWidget::rowErased(Row *row)
|
||||
{
|
||||
m_updating = true;
|
||||
if (row->erase->isChecked()) {
|
||||
row->state = Clear;
|
||||
row->edit->clear();
|
||||
} else {
|
||||
row->state = Keep;
|
||||
row->edit->setText(row->initial);
|
||||
}
|
||||
m_updating = false;
|
||||
updateRowLook(row);
|
||||
if (row->key == QETInformation::ELMT_FORMULA) {
|
||||
updateLabelRow();
|
||||
}
|
||||
changed();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief MultiElementInfoWidget::restoreRow
|
||||
Bring a field back to what the elements had. Does not apply.
|
||||
*/
|
||||
void MultiElementInfoWidget::restoreRow(Row *row)
|
||||
{
|
||||
const bool was_updating = m_updating;
|
||||
m_updating = true;
|
||||
row->state = Keep;
|
||||
row->erase->setChecked(false);
|
||||
row->edit->setText(row->initial);
|
||||
m_updating = was_updating;
|
||||
updateRowLook(row);
|
||||
if (!m_updating && row->key == QETInformation::ELMT_FORMULA) {
|
||||
updateLabelRow();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief MultiElementInfoWidget::restoreFlag
|
||||
Bring a check box back to what the elements had. Does not apply.
|
||||
*/
|
||||
void MultiElementInfoWidget::restoreFlag(Flag *flag)
|
||||
{
|
||||
flag->changed = false;
|
||||
flag->check->setTristate(flag->initial == Qt::PartiallyChecked);
|
||||
flag->check->setCheckState(flag->initial);
|
||||
flag->restore->setEnabled(false);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief MultiElementInfoWidget::flagClicked
|
||||
A click gives the box's state to every element. A box which started
|
||||
"partly checked" goes on as a plain on/off box: cycling back through
|
||||
"partly" is what its restore button is for.
|
||||
*/
|
||||
void MultiElementInfoWidget::flagClicked(Flag *flag)
|
||||
{
|
||||
if (flag->check->isTristate()) {
|
||||
flag->check->setTristate(false);
|
||||
if (flag->check->checkState() == Qt::PartiallyChecked) {
|
||||
flag->check->setCheckState(Qt::Checked);
|
||||
}
|
||||
}
|
||||
flag->changed = flag->check->checkState() != flag->initial;
|
||||
flag->restore->setEnabled(flag->changed);
|
||||
changed();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief MultiElementInfoWidget::updateRowLook
|
||||
A field whose values differ says so; a field which will change
|
||||
something has its restore button on and its name in bold.
|
||||
*/
|
||||
void MultiElementInfoWidget::updateRowLook(Row *row)
|
||||
{
|
||||
row->edit->setDisabled(row->state == Clear);
|
||||
if (row->state == Clear) {
|
||||
row->edit->setPlaceholderText(tr("Delete this text"));
|
||||
} else if (row->mixed) {
|
||||
row->edit->setPlaceholderText(tr("Multiple values"));
|
||||
} else {
|
||||
row->edit->setPlaceholderText(QString());
|
||||
}
|
||||
row->restore->setEnabled(row->state != Keep);
|
||||
QFont font = row->label->font();
|
||||
font.setBold(row->state != Keep);
|
||||
row->label->setFont(font);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief MultiElementInfoWidget::updateLabelRow
|
||||
As in the one-element editor, the label cannot be typed while the
|
||||
formula field holds a formula: the formula makes the label.
|
||||
*/
|
||||
void MultiElementInfoWidget::updateLabelRow()
|
||||
{
|
||||
Row *formula = rowForKey(QETInformation::ELMT_FORMULA);
|
||||
Row *label = rowForKey(QETInformation::ELMT_LABEL);
|
||||
if (formula && label) {
|
||||
const bool has_formula = !formula->edit->text().isEmpty();
|
||||
label->edit->setEnabled(!has_formula && label->state != Clear);
|
||||
label->erase->setEnabled(!has_formula);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief MultiElementInfoWidget::changed
|
||||
In live edit, every change is applied at once.
|
||||
*/
|
||||
void MultiElementInfoWidget::changed()
|
||||
{
|
||||
if (m_live_edit && !m_updating) {
|
||||
apply();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief MultiElementInfoWidget::elementInfoChanged
|
||||
The information of an element was changed by something else than
|
||||
this editor (undo, another editor): read the elements again.
|
||||
*/
|
||||
void MultiElementInfoWidget::elementInfoChanged()
|
||||
{
|
||||
if (m_applying || m_refresh_pending) {
|
||||
return;
|
||||
}
|
||||
//An undo changes every element one after the other:
|
||||
//read them once, when it is done.
|
||||
m_refresh_pending = true;
|
||||
QTimer::singleShot(0, this, [this]() {
|
||||
m_refresh_pending = false;
|
||||
updateUi();
|
||||
});
|
||||
}
|
||||
|
||||
MultiElementInfoWidget::Row *MultiElementInfoWidget::rowForKey(const QString &key) const
|
||||
{
|
||||
for (Row *row : m_rows) {
|
||||
if (row->key == key) {
|
||||
return row;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
Copyright 2006-2026 The QElectroTech Team
|
||||
This file is part of QElectroTech.
|
||||
|
||||
QElectroTech 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.
|
||||
|
||||
QElectroTech 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 QElectroTech. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifndef MULTIELEMENTINFOWIDGET_H
|
||||
#define MULTIELEMENTINFOWIDGET_H
|
||||
|
||||
#include "../PropertiesEditor/propertieseditorwidget.h"
|
||||
#include "../diagramcontext.h"
|
||||
|
||||
#include <QHash>
|
||||
#include <QPointer>
|
||||
|
||||
class Element;
|
||||
class QCheckBox;
|
||||
class QGraphicsItem;
|
||||
class QLabel;
|
||||
class QLineEdit;
|
||||
class QToolButton;
|
||||
|
||||
/**
|
||||
@brief The MultiElementInfoWidget class
|
||||
Edit the information (manufacturer, supplier, references...) of
|
||||
several elements at once (issue #1439).
|
||||
|
||||
Each field shows the value when every element has the same one, or
|
||||
"Multiple values" when they differ. A field left alone keeps each
|
||||
element's own value, a typed text is given to every element, and the
|
||||
erase box clears the field on every element. The restore button of a
|
||||
field, and reset(), bring back the values the elements had.
|
||||
All the changes of one apply are a single undo step.
|
||||
*/
|
||||
class MultiElementInfoWidget : public PropertiesEditorWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit MultiElementInfoWidget(const QList<Element *> &elements,
|
||||
int other_items = 0,
|
||||
QWidget *parent = nullptr);
|
||||
~MultiElementInfoWidget() override;
|
||||
|
||||
void setElements(const QList<Element *> &elements, int other_items = 0);
|
||||
QList<Element *> elements() const;
|
||||
|
||||
void apply() override;
|
||||
void reset() override;
|
||||
QUndoCommand *associatedUndo() const override;
|
||||
QString title() const override {return tr("Information");}
|
||||
void updateUi() override;
|
||||
bool setLiveEdit(bool live_edit) override;
|
||||
|
||||
static bool takesInformations(const Element *element);
|
||||
static QList<Element *> informationElements(const QList<QGraphicsItem *> &items);
|
||||
static QList<QGraphicsItem *> withoutOwnTexts(const QList<QGraphicsItem *> &items);
|
||||
|
||||
//What a field will do to the elements on apply
|
||||
enum FieldState {Keep, Set, Clear};
|
||||
FieldState fieldState(const QString &key) const;
|
||||
void setFieldText(const QString &key, const QString &text);
|
||||
void setFieldCleared(const QString &key, bool cleared);
|
||||
void restoreField(const QString &key);
|
||||
bool fieldIsMixed(const QString &key) const;
|
||||
|
||||
private:
|
||||
struct Row {
|
||||
QString key;
|
||||
QLabel *label = nullptr;
|
||||
QLineEdit *edit = nullptr;
|
||||
QCheckBox *erase = nullptr;
|
||||
QToolButton *restore = nullptr;
|
||||
QString initial;
|
||||
bool mixed = false;
|
||||
FieldState state = Keep;
|
||||
};
|
||||
struct Flag {
|
||||
QString key;
|
||||
QCheckBox *check = nullptr;
|
||||
QToolButton *restore = nullptr;
|
||||
Qt::CheckState initial = Qt::Unchecked;
|
||||
bool changed = false;
|
||||
};
|
||||
|
||||
void buildInterface();
|
||||
void takeSnapshot();
|
||||
void fillRows();
|
||||
void updateHeader();
|
||||
void updateSuggestions();
|
||||
void rowEdited(Row *row);
|
||||
void rowErased(Row *row);
|
||||
void restoreRow(Row *row);
|
||||
void restoreFlag(Flag *flag);
|
||||
void flagClicked(Flag *flag);
|
||||
void updateRowLook(Row *row);
|
||||
void updateLabelRow();
|
||||
void changed();
|
||||
void elementInfoChanged();
|
||||
Row *rowForKey(const QString &key) const;
|
||||
|
||||
QList<QPointer<Element>> m_elements;
|
||||
QHash<Element *, DiagramContext> m_snapshot;
|
||||
int m_other_items = 0;
|
||||
QLabel *m_header = nullptr;
|
||||
QList<Row *> m_rows;
|
||||
QList<Flag *> m_flags;
|
||||
bool m_updating = false;
|
||||
bool m_applying = false;
|
||||
bool m_refresh_pending = false;
|
||||
quint64 m_session = 0;
|
||||
};
|
||||
|
||||
#endif // MULTIELEMENTINFOWIDGET_H
|
||||
@@ -267,6 +267,13 @@ endfunction()
|
||||
# alone and with a group.
|
||||
qet_add_app_test(tst_rotateselectionimage tst_rotateselectionimage.cpp)
|
||||
|
||||
# Editing the information of several elements at once (#1439): kept, set
|
||||
# and cleared fields, restore and reset, one undo step, the selection
|
||||
# panel's editor and the Edit action.
|
||||
qet_add_app_test(tst_multielementinfo tst_multielementinfo.cpp)
|
||||
target_compile_definitions(tst_multielementinfo PRIVATE
|
||||
"QET_EXAMPLES_DIR=\"${QET_DIR}/examples\"")
|
||||
|
||||
# Accepting the settings dialog writes qet-assistant.json with the
|
||||
# collection paths just chosen, not the ones they replace.
|
||||
qet_add_app_test(tst_assistantinfopaths tst_assistantinfopaths.cpp)
|
||||
|
||||
@@ -0,0 +1,412 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#include "qetapptest.h"
|
||||
|
||||
#include "diagramcontent.h"
|
||||
#include "factory/propertieseditorfactory.h"
|
||||
#include "qetgraphicsitem/conductor.h"
|
||||
#include "qetgraphicsitem/dynamicelementtextitem.h"
|
||||
#include "qetgraphicsitem/element.h"
|
||||
#include "qetinformation.h"
|
||||
#include "projectview.h"
|
||||
#include "qetproject.h"
|
||||
#include "ui/elementinfopartwidget.h"
|
||||
#include "ui/elementinfowidget.h"
|
||||
#include "ui/elementpropertieswidget.h"
|
||||
#include "ui/multielementinfowidget.h"
|
||||
#include "undocommand/changeelementinformationcommand.h"
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QTabWidget>
|
||||
#include <QUndoStack>
|
||||
|
||||
// Editing the information of several elements at once (#1439): a field left
|
||||
// alone keeps each element's own value, a typed text goes to every element,
|
||||
// the erase box clears the field on every element, the restore button and
|
||||
// reset() bring back what the elements had, and one apply is one undo step.
|
||||
class tst_multielementinfo : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
QTemporaryDir m_dir;
|
||||
Diagram *m_diagram = nullptr;
|
||||
QList<Element *> m_elements;
|
||||
Conductor *m_conductor = nullptr;
|
||||
|
||||
QString info(Element *e, const QString &key) const
|
||||
{
|
||||
return e->elementInformations().value(key).toString();
|
||||
}
|
||||
|
||||
QUndoStack &stack() { return m_diagram->undoStack(); }
|
||||
|
||||
// The three test elements get known information, as one undo step
|
||||
// the test then leaves on the stack
|
||||
void setKnownInfo()
|
||||
{
|
||||
QMap<QPointer<Element>, QPair<DiagramContext, DiagramContext>> map;
|
||||
const QStringList suppliers {"S1", "S2", ""};
|
||||
for (int i = 0 ; i < 3 ; ++i)
|
||||
{
|
||||
Element *e = m_elements.at(i);
|
||||
DiagramContext info;
|
||||
info.addValue(QETInformation::ELMT_MANUFACTURER, "Maker");
|
||||
if (!suppliers.at(i).isEmpty())
|
||||
info.addValue(QETInformation::ELMT_SUPPLIER, suppliers.at(i));
|
||||
info.addValue(QETInformation::ELMT_DESCRIPTION, QString("desc %1").arg(i));
|
||||
info.addValue("my_custom_key", QString("custom %1").arg(i));
|
||||
if (i == 0)
|
||||
info.addValue("exclude_from_bom", "true");
|
||||
map.insert(e, qMakePair(e->elementInformations(), info));
|
||||
}
|
||||
stack().push(new ChangeElementInformationCommand(map));
|
||||
}
|
||||
|
||||
private slots:
|
||||
void initTestCase()
|
||||
{
|
||||
QETDiagramEditor *editor = QetAppTest::editor();
|
||||
QVERIFY(editor);
|
||||
// The first example folio with three plain elements and a wire
|
||||
const QStringList files = QDir(QET_EXAMPLES_DIR).entryList({"*.qet"}, QDir::Files, QDir::Name);
|
||||
for (const QString &file : files)
|
||||
{
|
||||
// A copy: opening a project leaves a lock file beside it
|
||||
const QString copy = m_dir.filePath(file);
|
||||
QVERIFY(QFile::copy(QDir(QET_EXAMPLES_DIR).filePath(file), copy));
|
||||
QVERIFY(editor->openAndAddProject(copy, false));
|
||||
QETProject *project = editor->currentProjectView()->project();
|
||||
for (Diagram *d : project->diagrams())
|
||||
{
|
||||
QList<Element *> simple;
|
||||
Conductor *conductor = nullptr;
|
||||
for (QGraphicsItem *qgi : d->items())
|
||||
{
|
||||
if (qgi->type() == Element::Type
|
||||
&& static_cast<Element *>(qgi)->linkType() == Element::Simple)
|
||||
simple << static_cast<Element *>(qgi);
|
||||
else if (!conductor && qgi->type() == Conductor::Type)
|
||||
conductor = static_cast<Conductor *>(qgi);
|
||||
}
|
||||
if (simple.size() >= 3 && conductor)
|
||||
{
|
||||
m_diagram = d;
|
||||
m_elements = simple.mid(0, 3);
|
||||
m_conductor = conductor;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (m_diagram)
|
||||
break;
|
||||
}
|
||||
QVERIFY2(m_diagram, "no example folio with three plain elements and a wire");
|
||||
setKnownInfo();
|
||||
}
|
||||
|
||||
void init()
|
||||
{
|
||||
stack().setClean();
|
||||
}
|
||||
|
||||
void cleanup()
|
||||
{
|
||||
// Back to the known information
|
||||
while (stack().index() > stack().cleanIndex())
|
||||
stack().undo();
|
||||
}
|
||||
|
||||
void showsSharedAndMixedValues()
|
||||
{
|
||||
MultiElementInfoWidget w(m_elements);
|
||||
QCOMPARE(w.fieldIsMixed(QETInformation::ELMT_MANUFACTURER), false);
|
||||
QCOMPARE(w.fieldIsMixed(QETInformation::ELMT_SUPPLIER), true);
|
||||
QCOMPARE(w.fieldIsMixed(QETInformation::ELMT_DESCRIPTION), true);
|
||||
// No element has a value: same (empty) value, not mixed
|
||||
QCOMPARE(w.fieldIsMixed(QETInformation::ELMT_QUANTITY), false);
|
||||
QVERIFY(!w.associatedUndo());
|
||||
}
|
||||
|
||||
void setGivesTextToEveryElementAsOneUndo()
|
||||
{
|
||||
MultiElementInfoWidget w(m_elements);
|
||||
w.setFieldText(QETInformation::ELMT_SUPPLIER, "ACME");
|
||||
QCOMPARE(w.fieldState(QETInformation::ELMT_SUPPLIER), MultiElementInfoWidget::Set);
|
||||
const int before = stack().index();
|
||||
w.apply();
|
||||
QCOMPARE(stack().index(), before + 1);
|
||||
for (Element *e : m_elements)
|
||||
{
|
||||
QCOMPARE(info(e, QETInformation::ELMT_SUPPLIER), QString("ACME"));
|
||||
// Untouched fields keep each element's own value
|
||||
QCOMPARE(info(e, QETInformation::ELMT_MANUFACTURER), QString("Maker"));
|
||||
QCOMPARE(info(e, "my_custom_key"),
|
||||
QString("custom %1").arg(m_elements.indexOf(e)));
|
||||
}
|
||||
QCOMPARE(info(m_elements[0], QETInformation::ELMT_DESCRIPTION), QString("desc 0"));
|
||||
|
||||
stack().undo();
|
||||
QCOMPARE(info(m_elements[0], QETInformation::ELMT_SUPPLIER), QString("S1"));
|
||||
QCOMPARE(info(m_elements[1], QETInformation::ELMT_SUPPLIER), QString("S2"));
|
||||
QVERIFY(!m_elements[2]->elementInformations().contains(QETInformation::ELMT_SUPPLIER));
|
||||
}
|
||||
|
||||
void eraseClearsEveryElement()
|
||||
{
|
||||
MultiElementInfoWidget w(m_elements);
|
||||
w.setFieldCleared(QETInformation::ELMT_DESCRIPTION, true);
|
||||
QCOMPARE(w.fieldState(QETInformation::ELMT_DESCRIPTION), MultiElementInfoWidget::Clear);
|
||||
w.apply();
|
||||
for (Element *e : m_elements) {
|
||||
QVERIFY(!e->elementInformations().contains(QETInformation::ELMT_DESCRIPTION));
|
||||
QCOMPARE(info(e, QETInformation::ELMT_MANUFACTURER), QString("Maker"));
|
||||
}
|
||||
}
|
||||
|
||||
// Emptying a field every element shares clears it, as in the
|
||||
// one-element editor
|
||||
void emptyingASharedValueClearsIt()
|
||||
{
|
||||
MultiElementInfoWidget w(m_elements);
|
||||
w.setFieldText(QETInformation::ELMT_MANUFACTURER, QString());
|
||||
QCOMPARE(w.fieldState(QETInformation::ELMT_MANUFACTURER), MultiElementInfoWidget::Set);
|
||||
w.apply();
|
||||
for (Element *e : m_elements)
|
||||
QVERIFY(!e->elementInformations().contains(QETInformation::ELMT_MANUFACTURER));
|
||||
}
|
||||
|
||||
// A mixed field emptied again, or a shared one typed back to its
|
||||
// value, changes nothing
|
||||
void typingBackToTheValueKeeps()
|
||||
{
|
||||
MultiElementInfoWidget w(m_elements);
|
||||
w.setFieldText(QETInformation::ELMT_SUPPLIER, "x");
|
||||
w.setFieldText(QETInformation::ELMT_SUPPLIER, QString());
|
||||
QCOMPARE(w.fieldState(QETInformation::ELMT_SUPPLIER), MultiElementInfoWidget::Keep);
|
||||
w.setFieldText(QETInformation::ELMT_MANUFACTURER, "Make");
|
||||
w.setFieldText(QETInformation::ELMT_MANUFACTURER, "Maker");
|
||||
QCOMPARE(w.fieldState(QETInformation::ELMT_MANUFACTURER), MultiElementInfoWidget::Keep);
|
||||
QVERIFY(!w.associatedUndo());
|
||||
}
|
||||
|
||||
void restoreAndResetBringBackTheValues()
|
||||
{
|
||||
MultiElementInfoWidget w(m_elements);
|
||||
w.setFieldText(QETInformation::ELMT_SUPPLIER, "ACME");
|
||||
w.setFieldCleared(QETInformation::ELMT_DESCRIPTION, true);
|
||||
w.restoreField(QETInformation::ELMT_SUPPLIER);
|
||||
QCOMPARE(w.fieldState(QETInformation::ELMT_SUPPLIER), MultiElementInfoWidget::Keep);
|
||||
QCOMPARE(w.fieldState(QETInformation::ELMT_DESCRIPTION), MultiElementInfoWidget::Clear);
|
||||
w.reset();
|
||||
QCOMPARE(w.fieldState(QETInformation::ELMT_DESCRIPTION), MultiElementInfoWidget::Keep);
|
||||
QVERIFY(!w.associatedUndo());
|
||||
}
|
||||
|
||||
// The selection panel applies each keystroke; they make one undo
|
||||
// step, and restoring the field in the panel undoes it
|
||||
void liveEditMergesAndRestores()
|
||||
{
|
||||
MultiElementInfoWidget w(m_elements);
|
||||
w.setLiveEdit(true);
|
||||
const int before = stack().index();
|
||||
w.setFieldText(QETInformation::ELMT_SUPPLIER, "A");
|
||||
w.setFieldText(QETInformation::ELMT_SUPPLIER, "AC");
|
||||
w.setFieldText(QETInformation::ELMT_SUPPLIER, "ACME");
|
||||
QCOMPARE(stack().index(), before + 1);
|
||||
for (Element *e : m_elements)
|
||||
QCOMPARE(info(e, QETInformation::ELMT_SUPPLIER), QString("ACME"));
|
||||
|
||||
w.restoreField(QETInformation::ELMT_SUPPLIER);
|
||||
QCOMPARE(info(m_elements[0], QETInformation::ELMT_SUPPLIER), QString("S1"));
|
||||
QCOMPARE(info(m_elements[1], QETInformation::ELMT_SUPPLIER), QString("S2"));
|
||||
QVERIFY(!m_elements[2]->elementInformations().contains(QETInformation::ELMT_SUPPLIER));
|
||||
}
|
||||
|
||||
// The first live change never joins an earlier change to the
|
||||
// same elements (here the paste-like command of initTestCase)
|
||||
void liveEditDoesNotJoinEarlierChange()
|
||||
{
|
||||
QMap<QPointer<Element>, QPair<DiagramContext, DiagramContext>> map;
|
||||
for (Element *e : m_elements) {
|
||||
DiagramContext info = e->elementInformations();
|
||||
info.addValue(QETInformation::ELMT_PLANT, "P1");
|
||||
map.insert(e, qMakePair(e->elementInformations(), info));
|
||||
}
|
||||
stack().push(new ChangeElementInformationCommand(map));
|
||||
const int before = stack().index();
|
||||
|
||||
MultiElementInfoWidget w(m_elements);
|
||||
w.setLiveEdit(true);
|
||||
w.setFieldText(QETInformation::ELMT_SUPPLIER, "ACME");
|
||||
QCOMPARE(stack().index(), before + 1);
|
||||
stack().undo();
|
||||
for (Element *e : m_elements)
|
||||
QCOMPARE(info(e, QETInformation::ELMT_PLANT), QString("P1"));
|
||||
}
|
||||
|
||||
// An undo from outside the editor shows again what the elements have
|
||||
void undoRefreshesTheEditor()
|
||||
{
|
||||
MultiElementInfoWidget w(m_elements);
|
||||
w.setLiveEdit(true);
|
||||
w.setFieldText(QETInformation::ELMT_SUPPLIER, "ACME");
|
||||
QCOMPARE(w.fieldState(QETInformation::ELMT_SUPPLIER), MultiElementInfoWidget::Set);
|
||||
stack().undo();
|
||||
QCoreApplication::processEvents();
|
||||
QCOMPARE(w.fieldIsMixed(QETInformation::ELMT_SUPPLIER), true);
|
||||
QCOMPARE(w.fieldState(QETInformation::ELMT_SUPPLIER), MultiElementInfoWidget::Keep);
|
||||
}
|
||||
|
||||
// "Exclude from the bill of materials": partly checked when the
|
||||
// elements differ, a click sets it on every element
|
||||
void flagMixedThenSet()
|
||||
{
|
||||
MultiElementInfoWidget w(m_elements);
|
||||
QCheckBox *bom = nullptr;
|
||||
for (QCheckBox *cb : w.findChildren<QCheckBox *>())
|
||||
if (cb->text() == QObject::tr("Exclude from the bill of materials"))
|
||||
bom = cb;
|
||||
QVERIFY(bom);
|
||||
QCOMPARE(bom->checkState(), Qt::PartiallyChecked);
|
||||
bom->click();
|
||||
w.apply();
|
||||
for (Element *e : m_elements)
|
||||
QVERIFY(QET::infoFlagIsTrue(info(e, "exclude_from_bom")));
|
||||
}
|
||||
|
||||
void formulaIdKeptOnlyWithItsFormula()
|
||||
{
|
||||
QMap<QPointer<Element>, QPair<DiagramContext, DiagramContext>> map;
|
||||
for (Element *e : m_elements)
|
||||
{
|
||||
DiagramContext info = e->elementInformations();
|
||||
info.addValue(QETInformation::ELMT_FORMULA, "K%{id}");
|
||||
info.addValue(QETInformation::ELMT_FORMULA_ID, "{00000000-0000-0000-0000-000000000001}", false);
|
||||
map.insert(e, qMakePair(e->elementInformations(), info));
|
||||
}
|
||||
// Element drops an id naming no numbering of the project;
|
||||
// what is checked is that the editor itself keeps or drops it
|
||||
stack().push(new ChangeElementInformationCommand(map));
|
||||
const bool stored = m_elements[0]->elementInformations().contains(QETInformation::ELMT_FORMULA_ID);
|
||||
|
||||
MultiElementInfoWidget w(m_elements);
|
||||
w.setFieldText(QETInformation::ELMT_SUPPLIER, "ACME");
|
||||
w.apply();
|
||||
QCOMPARE(m_elements[0]->elementInformations().contains(QETInformation::ELMT_FORMULA_ID), stored);
|
||||
|
||||
MultiElementInfoWidget w2(m_elements);
|
||||
w2.setFieldText(QETInformation::ELMT_FORMULA, "Q%{id}");
|
||||
w2.apply();
|
||||
for (Element *e : m_elements) {
|
||||
QVERIFY(!e->elementInformations().contains(QETInformation::ELMT_FORMULA_ID));
|
||||
QCOMPARE(info(e, QETInformation::ELMT_FORMULA), QString("Q%{id}"));
|
||||
}
|
||||
}
|
||||
|
||||
// The selection panel: one element, several, and a mix
|
||||
void factoryPicksTheEditor()
|
||||
{
|
||||
QList<QGraphicsItem *> one {m_elements[0]};
|
||||
std::unique_ptr<PropertiesEditorWidget> e1(PropertiesEditorFactory::propertiesEditor(one, nullptr));
|
||||
QVERIFY(qobject_cast<ElementPropertiesWidget *>(e1.get()));
|
||||
|
||||
QList<QGraphicsItem *> two {m_elements[0], m_elements[1]};
|
||||
std::unique_ptr<PropertiesEditorWidget> e2(PropertiesEditorFactory::propertiesEditor(two, nullptr));
|
||||
auto multi = qobject_cast<MultiElementInfoWidget *>(e2.get());
|
||||
QVERIFY(multi);
|
||||
QCOMPARE(multi->elements().size(), 2);
|
||||
|
||||
// Same kind of editor: reused for the new selection
|
||||
QList<QGraphicsItem *> mix {m_elements[0], m_elements[1], m_elements[2], m_conductor};
|
||||
PropertiesEditorWidget *e3 = PropertiesEditorFactory::propertiesEditor(mix, e2.get());
|
||||
QCOMPARE(e3, e2.get());
|
||||
QCOMPARE(multi->elements().size(), 3);
|
||||
|
||||
// Select all takes the elements' own texts: not other items
|
||||
QList<QGraphicsItem *> with_texts {m_elements[0], m_elements[1]};
|
||||
for (QGraphicsItem *child : m_elements[0]->childItems())
|
||||
if (child->type() == DynamicElementTextItem::Type)
|
||||
with_texts << child;
|
||||
QVERIFY(with_texts.size() > 2);
|
||||
QCOMPARE(MultiElementInfoWidget::withoutOwnTexts(with_texts).size(), 2);
|
||||
// A text alone, or with another element, stays
|
||||
QList<QGraphicsItem *> other_text {m_elements[1], with_texts.last()};
|
||||
QCOMPARE(MultiElementInfoWidget::withoutOwnTexts(other_text).size(), 2);
|
||||
|
||||
QList<QGraphicsItem *> wire {m_conductor};
|
||||
std::unique_ptr<PropertiesEditorWidget> e4(PropertiesEditorFactory::propertiesEditor(wire, nullptr));
|
||||
QVERIFY(!qobject_cast<MultiElementInfoWidget *>(e4.get()));
|
||||
}
|
||||
|
||||
// Edit (Ctrl+E) is offered for several elements, not for a mix
|
||||
void editActionForSeveralElements()
|
||||
{
|
||||
QETDiagramEditor *editor = QetAppTest::editor();
|
||||
QAction *edit = QetAppTest::action("edit_selected_element");
|
||||
QVERIFY(edit);
|
||||
|
||||
m_diagram->clearSelection();
|
||||
m_elements[0]->setSelected(true);
|
||||
m_elements[1]->setSelected(true);
|
||||
editor->slot_updateComplexActions();
|
||||
QVERIFY(edit->isEnabled());
|
||||
|
||||
// with their own texts, as select all gives them
|
||||
for (QGraphicsItem *child : m_elements[0]->childItems())
|
||||
child->setSelected(true);
|
||||
editor->slot_updateComplexActions();
|
||||
QVERIFY(edit->isEnabled());
|
||||
|
||||
m_conductor->setSelected(true);
|
||||
editor->slot_updateComplexActions();
|
||||
QVERIFY(!edit->isEnabled());
|
||||
m_diagram->clearSelection();
|
||||
}
|
||||
|
||||
// The selection panel builds a new editor when the selection goes
|
||||
// from one element to several and back: it opens on the tab
|
||||
// picked last there, not on the first one
|
||||
void panelKeepsTheTabPicked()
|
||||
{
|
||||
auto tabsOf = [](QWidget *w) { return w->findChild<QTabWidget *>(); };
|
||||
int general = -1;
|
||||
{
|
||||
ElementPropertiesWidget first(m_elements[0]);
|
||||
first.setLiveEdit(true);
|
||||
QTabWidget *tabs = tabsOf(&first);
|
||||
QVERIFY(tabs);
|
||||
for (int i = 0 ; i < tabs->count() ; ++i)
|
||||
if (tabs->tabText(i) == QObject::tr("General"))
|
||||
general = i;
|
||||
QVERIFY(general > 0);
|
||||
QVERIFY(tabs->currentIndex() != general);
|
||||
emit tabs->tabBarClicked(general);
|
||||
tabs->setCurrentIndex(general);
|
||||
}
|
||||
ElementPropertiesWidget second(m_elements[1]);
|
||||
second.setLiveEdit(true);
|
||||
QCOMPARE(tabsOf(&second)->currentIndex(), general);
|
||||
|
||||
// A dialog (not live) still opens on the first tab
|
||||
ElementPropertiesWidget dialog(m_elements[1]);
|
||||
QCOMPARE(tabsOf(&dialog)->currentIndex(), 0);
|
||||
}
|
||||
|
||||
// The Reset button of the one-element dialog brings back the
|
||||
// Information tab's values
|
||||
void oneElementResetRestores()
|
||||
{
|
||||
ElementInfoWidget w(m_elements[0]);
|
||||
ElementInfoPartWidget *part = nullptr;
|
||||
for (auto *p : w.findChildren<ElementInfoPartWidget *>())
|
||||
if (p->key() == QETInformation::ELMT_SUPPLIER)
|
||||
part = p;
|
||||
QVERIFY(part);
|
||||
part->setText("changed");
|
||||
QCOMPARE(w.currentInfo().value(QETInformation::ELMT_SUPPLIER).toString(), QString("changed"));
|
||||
w.reset();
|
||||
QCOMPARE(w.currentInfo().value(QETInformation::ELMT_SUPPLIER).toString(), QString("S1"));
|
||||
}
|
||||
};
|
||||
|
||||
QET_APP_TEST_MAIN(tst_multielementinfo)
|
||||
|
||||
#include "tst_multielementinfo.moc"
|
||||
Reference in New Issue
Block a user