Merge master into feat/1071-dxf-layers

This commit is contained in:
ispyisail
2026-09-28 07:22:31 +13:00
27 changed files with 1025 additions and 20 deletions
+5
View File
@@ -234,6 +234,8 @@ set(QET_SRC_FILES
${QET_DIR}/sources/exportpropertieswidget.h
${QET_DIR}/sources/genericpanel.cpp
${QET_DIR}/sources/genericpanel.h
${QET_DIR}/sources/itemgroups.cpp
${QET_DIR}/sources/itemgroups.h
${QET_DIR}/sources/lastusedstyle.cpp
${QET_DIR}/sources/lastusedstyle.h
${QET_DIR}/sources/machine_info.cpp
@@ -327,6 +329,7 @@ set(QET_SRC_FILES
${QET_DIR}/sources/autoNum/ui/selectautonumw.cpp
${QET_DIR}/sources/autoNum/ui/selectautonumw.h
${QET_DIR}/sources/dataBase/legacyelementtypes.h
${QET_DIR}/sources/dataBase/projectdatabase.cpp
${QET_DIR}/sources/dataBase/projectdatabase.h
${QET_DIR}/sources/dataBase/sqlreadonly.cpp
@@ -838,6 +841,8 @@ set(QET_SRC_FILES
${QET_DIR}/sources/undocommand/removediagramcommand.h
${QET_DIR}/sources/undocommand/setautonumcontextcommand.cpp
${QET_DIR}/sources/undocommand/setautonumcontextcommand.h
${QET_DIR}/sources/undocommand/groupitemscommand.cpp
${QET_DIR}/sources/undocommand/groupitemscommand.h
${QET_DIR}/sources/undocommand/alignselectioncommand.cpp
${QET_DIR}/sources/undocommand/alignselectioncommand.h
${QET_DIR}/sources/undocommand/rotateselectioncommand.cpp
@@ -404,10 +404,16 @@ void ElementPickerPopup::setCommands(const QStringList &ids)
button->setIcon(action->icon());
}
const QKeySequence key = action->shortcut();
button->setToolTip(key.isEmpty()
? text
: QStringLiteral("%1 (%2)").arg(
text, key.toString(QKeySequence::NativeText)));
QString tip = key.isEmpty()
? text
: QStringLiteral("%1 (%2)").arg(
text, key.toString(QKeySequence::NativeText));
//The status tip says what the command does, which a short
//name such as "Orienter les textes" does not
if (!action->statusTip().isEmpty()) {
tip += QLatin1Char('\n') + action->statusTip();
}
button->setToolTip(tip);
button->setEnabled(action->isEnabled());
button->setFocusPolicy(Qt::NoFocus);
connect(button, &QToolButton::clicked, this, [this, action]() {
@@ -1103,7 +1103,32 @@ void ElementsCollectionWidget::search()
return;
}
showFlatResults(rankedSearch(text, m_showed_index));
QSettings settings;
if (settings.value(QStringLiteral("elementscollection/search-flat-list"),
true).toBool())
{
showFlatResults(rankedSearch(text, m_showed_index));
return;
}
//Tree search: hide what does not match, expand what does.
clearFlatResults();
hideCollection(true);
const QStringList text_list = text.split("+", Qt::SkipEmptyParts);
QModelIndexList match_index;
for (const QString &txt : text_list) {
match_index << m_model->match(m_showed_index.isValid()
? m_model->index(0,0,m_showed_index)
: m_model->index(0,0),
Qt::UserRole+1,
QVariant(txt),
-1,
Qt::MatchContains
| Qt::MatchRecursive);
}
for (const QModelIndex &index : std::as_const(match_index))
showAndExpandItem(index);
}
/**
+69
View File
@@ -0,0 +1,69 @@
/*
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 LEGACYELEMENTTYPES_H
#define LEGACYELEMENTTYPES_H
#include <QRegularExpression>
#include <QString>
/**
Element type names in queries saved by versions before June 2022.
The project database then held Element::linkTypeToString() ("Simple",
"Terminale", ...); since it holds ElementData::typeToString()
("simple", "terminal", ...). SQLite compares text case-sensitively, so
a nomenclature table saved with element_type = 'Simple' silently lost
every simple element, and its continuation tables were deleted as
empty.
*/
namespace LegacyElementTypes
{
/**
@return query with every element_type = '<old name>' comparison
rewritten to the current name. Anything else is left as it is,
so a query saved by a current version comes back unchanged.
*/
inline QString upgradeQuery(const QString &query)
{
static const QRegularExpression re(QStringLiteral(
"element_type\\s*=\\s*'(Simple|NextReport|PreviousReport|Master"
"|Slave|Terminale|Thumbnail)'"));
QString upgraded = query;
auto it = re.globalMatch(query);
int shift = 0;
while (it.hasNext())
{
const auto match = it.next();
const QString old_name = match.captured(1);
QString new_name;
if (old_name == QLatin1String("Simple")) new_name = QStringLiteral("simple");
else if (old_name == QLatin1String("NextReport")) new_name = QStringLiteral("next_report");
else if (old_name == QLatin1String("PreviousReport")) new_name = QStringLiteral("previous_report");
else if (old_name == QLatin1String("Master")) new_name = QStringLiteral("master");
else if (old_name == QLatin1String("Slave")) new_name = QStringLiteral("slave");
else if (old_name == QLatin1String("Terminale")) new_name = QStringLiteral("terminal");
else new_name = QStringLiteral("thumbnail");
upgraded.replace(match.capturedStart(1) + shift, old_name.size(), new_name);
shift += new_name.size() - old_name.size();
}
return upgraded;
}
}
#endif // LEGACYELEMENTTYPES_H
+43 -4
View File
@@ -22,6 +22,7 @@
#include "../diagram.h"
#include "../diagramposition.h"
#include "../elementprovider.h"
#include "../itemgroups.h"
#include "../qetapp.h"
#include "../qetgraphicsitem/conductor.h"
#include "../qetgraphicsitem/diagramimageitem.h"
@@ -669,6 +670,13 @@ QUuid drawingItemUuid(QObject *object)
return QUuid();
}
/// The group_uuid column of @p item's row: its group, or NULL.
QVariant groupValue(const QGraphicsItem *item)
{
const QUuid group = ItemGroups::groupOf(item);
return group.isNull() ? QVariant() : QVariant(group.toString());
}
} // namespace
/**
@@ -751,6 +759,33 @@ void projectDataBase::removeDrawingItem(QGraphicsItem *item)
forgetDrawingItem(object);
}
/**
@brief projectDataBase::itemGroupChanged
@a item joined or left a group (discussion #1070). An element's row is
updated at once; a drawing item's is queued like any other change to it.
@param item
*/
void projectDataBase::itemGroupChanged(QGraphicsItem *item)
{
if (auto element = qgraphicsitem_cast<Element *>(item))
{
QSqlQuery update(m_data_base);
update.prepare(QStringLiteral("UPDATE element SET group_uuid = :group_uuid WHERE uuid = :uuid"));
update.bindValue(QStringLiteral(":group_uuid"), groupValue(element));
update.bindValue(QStringLiteral(":uuid"), element->uuid().toString());
if (!update.exec()) {
qDebug() << "projectDataBase::itemGroupChanged update error : " << update.lastError();
}
m_content_changed = true;
return;
}
QGraphicsObject *object = item ? item->toGraphicsObject() : nullptr;
if (object && !drawingItemTable(object).isEmpty()) {
m_dirty_drawing_items.insert(object);
}
}
/**
@brief projectDataBase::drawingItemChanged
Queue the sender's row to be rewritten.
@@ -884,6 +919,7 @@ bool projectDataBase::writeDrawingItem(QObject *object)
query->bindValue(QStringLiteral(":y"), rect.y());
query->bindValue(QStringLiteral(":width"), rect.width());
query->bindValue(QStringLiteral(":height"), rect.height());
query->bindValue(QStringLiteral(":group_uuid"), groupValue(item));
if (!query->exec()) {
qDebug() << "projectDataBase::writeDrawingItem error : " << query->lastError();
return true;
@@ -985,6 +1021,7 @@ bool projectDataBase::createDataBase()
"pos VARCHAR(6) NOT NULL,"
"type VARCHAR(50),"
"sub_type VARCHAR(50),"
"group_uuid VARCHAR(50),"
"FOREIGN KEY (diagram_uuid) REFERENCES diagram (uuid)"
")");
if (!query_.exec(element_table)) {
@@ -1085,7 +1122,8 @@ bool projectDataBase::createDataBase()
"uuid VARCHAR(50) PRIMARY KEY NOT NULL, "
"diagram_uuid VARCHAR(50) NOT NULL, "
"pos VARCHAR(6), "
"x REAL, y REAL, width REAL, height REAL, ");
"x REAL, y REAL, width REAL, height REAL, "
"group_uuid VARCHAR(50), ");
for (const QString &table : {
QStringLiteral("CREATE TABLE shape (") + drawing_columns +
"type VARCHAR(20), color VARCHAR(20), fill VARCHAR(20), "
@@ -1516,8 +1554,8 @@ void projectDataBase::prepareQuery()
//DRAWING ITEMS. OR REPLACE: a row is rewritten in place on every
//change, see writeDrawingItem().
const QString drawing_columns("uuid, diagram_uuid, pos, x, y, width, height");
const QString drawing_values(":uuid, :diagram_uuid, :pos, :x, :y, :width, :height");
const QString drawing_columns("uuid, diagram_uuid, pos, x, y, width, height, group_uuid");
const QString drawing_values(":uuid, :diagram_uuid, :pos, :x, :y, :width, :height, :group_uuid");
m_insert_shape_query = QSqlQuery(m_data_base);
m_insert_shape_query.prepare("INSERT OR REPLACE INTO shape (" + drawing_columns +
", type, color, fill) VALUES (" + drawing_values +
@@ -1561,7 +1599,7 @@ void projectDataBase::prepareQuery()
m_diagram_info_order_changed.prepare("UPDATE diagram_info SET folio = :folio WHERE diagram_uuid = :uuid");
//INSERT ELEMENT
QString insert_element_query("INSERT INTO element (uuid, diagram_uuid, pos, type, sub_type) VALUES (:uuid, :diagram_uuid, :pos, :type, :sub_type)");
QString insert_element_query("INSERT INTO element (uuid, diagram_uuid, pos, type, sub_type, group_uuid) VALUES (:uuid, :diagram_uuid, :pos, :type, :sub_type, :group_uuid)");
m_insert_elements_query = QSqlQuery(m_data_base);
m_insert_elements_query.prepare(insert_element_query);
@@ -1675,6 +1713,7 @@ void projectDataBase::bindElementValues(QSqlQuery &query, Element *element, Diag
query.bindValue(QStringLiteral(":pos"), diagram->convertPosition(element->scenePos()).toString());
query.bindValue(QStringLiteral(":type"), element_data.typeToString());
query.bindValue(QStringLiteral(":sub_type"), element_data.masterTypeToString());
query.bindValue(QStringLiteral(":group_uuid"), groupValue(element));
}
/**
+1
View File
@@ -105,6 +105,7 @@ class projectDataBase : public QObject
//furniture. Anything else passed here is ignored.
void addDrawingItem (QGraphicsItem *item);
void removeDrawingItem (QGraphicsItem *item);
void itemGroupChanged (QGraphicsItem *item);
private slots:
//Refresh the sender()'s row after Conductor::setProperties().
+4 -1
View File
@@ -20,6 +20,7 @@
#include "../../properties/elementdata.h"
#include "../../qetapp.h"
#include "../../qetinformation.h"
#include "../legacyelementtypes.h"
#include "../projectdatabase.h"
#include "ui_elementquerywidget.h"
@@ -674,7 +675,9 @@ void ElementQueryWidget::on_m_load_pb_clicked()
auto value_object = value.toObject();
if (value_object.value("query").isString()) {
setQuery(value_object.value("query").toString());
//A query saved before June 2022 names element types the old way
setQuery(LegacyElementTypes::upgradeQuery(
value_object.value("query").toString()));
}
}
+85 -2
View File
@@ -42,7 +42,9 @@
#include "qetinformation.h"
#include "qetproject.h"
#include "diagramsortkeys.h"
#include "itemgroups.h"
#include "textgrid.h"
#include <QGraphicsView>
#include <QTextStream>
#include <algorithm>
#include <climits>
@@ -121,6 +123,7 @@ namespace {
for (T *item : items) {
Entry entry{stack_rank.value(item, INT_MAX), QString(),
item->toXml(document)};
ItemGroups::write(entry.xml, item);
// Only an item the stacking query missed needs a tiebreak.
if (entry.rank == INT_MAX) {
QTextStream stream(&entry.xml_text);
@@ -210,6 +213,7 @@ Diagram::Diagram(QETProject *project) :
connect(&border_and_titleblock,
&BorderTitleBlock::needTitleBlockTemplate,
this, &Diagram::setTitleBlockTemplate);
connect(&border_and_titleblock,
&BorderTitleBlock::informationChanged,
this, &Diagram::titleChanged);
@@ -432,7 +436,9 @@ void Diagram::mousePressEvent(QGraphicsSceneMouseEvent *event)
}
}
rememberSelection();
QGraphicsScene::mousePressEvent(event);
completeGroupSelection();
}
/**
@@ -471,6 +477,9 @@ void Diagram::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
}
QGraphicsScene::mouseReleaseEvent(event);
//A click on an already selected item changes the selection on
//release, not on press (Ctrl toggles it, a plain click keeps only it).
completeGroupSelection();
}
/**
@@ -1245,8 +1254,9 @@ QDomDocument Diagram::toXml(bool whole_content, bool is_copy_command) {
if (!list_elements.isEmpty()) {
auto dom_elements = document.createElement(QStringLiteral("elements"));
for (auto elmt : list_elements) {
dom_elements.appendChild(elmt->toXml(document,
table_adr_id));
QDomElement dom_element = elmt->toXml(document, table_adr_id);
ItemGroups::write(dom_element, elmt);
dom_elements.appendChild(dom_element);
// If copy is active we have to undo the changes we have made during creating(filling) 'list_elements'
if(is_copy_command && (elmt->linkType() == Element::Slave || elmt->linkType()&Element::AllReport))
restoreText(elmt);
@@ -1666,6 +1676,7 @@ bool Diagram::fromXml(QDomElement &document,
delete nvel_elmt;
qDebug() << QStringLiteral("Diagram::fromXml() : Le chargement des parametres d'un element a echoue");
} else {
ItemGroups::setGroup(nvel_elmt, ItemGroups::read(element_xml));
added_elements << nvel_elmt;
}
}
@@ -1701,6 +1712,7 @@ bool Diagram::fromXml(QDomElement &document,
IndependentTextItem *iti = new IndependentTextItem();
iti -> fromXml(text_xml);
settle_uuid(iti, text_xml, QStringLiteral("input"), added_texts.size());
ItemGroups::setGroup(iti, ItemGroups::read(text_xml));
addItem(iti);
added_texts << iti;
}
@@ -1713,6 +1725,7 @@ bool Diagram::fromXml(QDomElement &document,
DiagramImageItem *dii = new DiagramImageItem ();
dii -> fromXml(image_xml);
settle_uuid(dii, image_xml, QStringLiteral("image"), added_images.size());
ItemGroups::setGroup(dii, ItemGroups::read(image_xml));
addItem(dii);
added_images << dii;
}
@@ -1725,6 +1738,7 @@ bool Diagram::fromXml(QDomElement &document,
QetShapeItem *dii = new QetShapeItem (QPointF(0,0));
dii -> fromXml(shape_xml);
settle_uuid(dii, shape_xml, QStringLiteral("shape"), added_shapes.size());
ItemGroups::setGroup(dii, ItemGroups::read(shape_xml));
addItem(dii);
added_shapes << dii;
}
@@ -2127,6 +2141,75 @@ void Diagram::selectAllTextFields()
emit selectionChanged();
}
/**
@brief Diagram::setItemGroup
Put @a item in @a group, or take it out of its group if @a group is
null, and keep its row in the project database in step.
@param item
@param group
*/
void Diagram::setItemGroup(QGraphicsItem *item, const QUuid &group)
{
ItemGroups::setGroup(item, group);
if (m_project) {
m_project->dataBase()->itemGroupChanged(item);
}
}
/**
@brief Diagram::completeGroupSelection
Make the selection whole groups again after the user changed it with a
click, see ItemGroups::completeSelection(). Called from the mouse
handlers and, when a rubber band is released, from DiagramView -- not
on every selectionChanged(): code that selects items itself (export,
search, Tab cycling) deselects and reselects one item at a time and
must not have groups pulled back in behind it.
Left alone while a rubber band is being dragged, which reselects exactly
what it covers on every mouse step.
*/
void Diagram::completeGroupSelection()
{
for (QGraphicsView *view : views()) {
if (!view->rubberBandRect().isNull()) {
return;
}
}
QList<QGraphicsItem *> previous;
for (const QPointer<QGraphicsObject> &item : std::as_const(m_previous_selection)) {
if (item) {
previous << item.data();
}
}
//One selectionChanged() for the whole group, not one per member:
//the properties dock rebuilds on each.
blockSignals(true);
const bool changed = ItemGroups::completeSelection(
this, previous,
QApplication::keyboardModifiers().testFlag(Qt::ControlModifier));
blockSignals(false);
if (changed) {
emit selectionChanged();
}
rememberSelection();
}
/**
@brief Diagram::rememberSelection
Keep the current selection, the "before" completeGroupSelection() needs
to tell a member being Ctrl+clicked off.
*/
void Diagram::rememberSelection()
{
m_previous_selection.clear();
for (QGraphicsItem *item : selectedItems()) {
if (QGraphicsObject *object = item->toGraphicsObject()) {
m_previous_selection << object;
}
}
}
/**
@brief Diagram::selectNextItem
Select the next (or, if @a forward is false, the previous) selectable
+7
View File
@@ -27,6 +27,7 @@
#include "qgimanager.h"
#include <QHash>
#include <QPointer>
#include <QUuid>
#include <QtWidgets>
#include <QtXml>
@@ -138,6 +139,10 @@ class Diagram : public QGraphicsScene
bool m_freeze_new_conductors_;
QUuid m_uuid = QUuid::createUuid();
//Selection before the current click, see completeGroupSelection()
QList<QPointer<QGraphicsObject>> m_previous_selection;
void rememberSelection();
bool uuidUsedByOtherDiagram(const QUuid &uuid) const;
QUuid derivedUuid(const QDomElement &root, const QString &reason) const;
@@ -282,6 +287,7 @@ class Diagram : public QGraphicsScene
const QString& title, const QString& seq,
NumerotationContext *nc);
void changeZValue(QET::DepthOption option);
void setItemGroup(QGraphicsItem *item, const QUuid &group);
public slots:
void adjustSceneRect ();
@@ -299,6 +305,7 @@ class Diagram : public QGraphicsScene
void invertSelection();
void selectAllConductors();
void selectAllTextFields();
void completeGroupSelection();
signals:
void showDiagram (Diagram *);
+22
View File
@@ -18,6 +18,7 @@
#include "diagramcommands.h"
#include "diagram.h"
#include "itemgroups.h"
#include "qetgraphicsitem/conductortextitem.h"
#include "qetgraphicsitem/diagramimageitem.h"
#include "qetgraphicsitem/dynamicelementtextitem.h"
@@ -216,6 +217,27 @@ void PasteDiagramCommand::redo()
}
}
}
//Pasted groups become new groups: the members of one source
//group all get the same new uuid, never the source's, or the
//copy would join the original's group. After the elements got
//their own uuids: the database row is found by uuid, and before
//that it would have been the source element's row.
QHash<QUuid, QUuid> renewed_groups;
for (QGraphicsItem *item : content.items(DiagramContent::Elements
| DiagramContent::TextFields
| DiagramContent::Images
| DiagramContent::Shapes))
{
const QUuid source_group = ItemGroups::groupOf(item);
if (source_group.isNull()) {
continue;
}
if (!renewed_groups.contains(source_group)) {
renewed_groups.insert(source_group, QUuid::createUuid());
}
diagram -> setItemGroup(item, renewed_groups.value(source_group));
}
}
else
{
+6 -1
View File
@@ -23,6 +23,7 @@
#include <QHBoxLayout>
#include <QMouseEvent>
#include <QSettings>
#include <QStringBuilder>
#include <QToolButton>
namespace {
@@ -92,7 +93,11 @@ void DiagramContextToolbar::showAt(const QPoint &viewport_pos,
} else {
button->setIcon(action->icon());
}
button->setToolTip(text);
//The status tip says what the command does, which a short
//name such as "Orienter les textes" does not
button->setToolTip(action->statusTip().isEmpty()
? text
: text % QLatin1Char('\n') % action->statusTip());
button->setEnabled(action->isEnabled());
connect(button, &QToolButton::clicked, action, &QAction::trigger);
m_layout->addWidget(button);
+7
View File
@@ -122,6 +122,13 @@ DiagramView::DiagramView(Diagram *diagram, QWidget *parent) :
});
connect(m_diagram, &Diagram::showDiagram, this, &DiagramView::showDiagram);
//The diagram leaves group completion alone while a rubber band is
//dragged; finish it when the band is released (null rect).
connect(this, &QGraphicsView::rubberBandChanged, this, [this](QRect rect) {
if (rect.isNull()) {
m_diagram->completeGroupSelection();
}
});
connect(m_diagram, &QGraphicsScene::sceneRectChanged, this, &DiagramView::adjustSceneRect);
connect(&(m_diagram -> border_and_titleblock), &BorderTitleBlock::informationChanged, this, &DiagramView::updateWindowTitle);
connect(diagram, &Diagram::findElementRequired, this, &DiagramView::findElementRequired);
+15
View File
@@ -32,6 +32,7 @@
#include "qetgraphicsitem/diagramimageitem.h"
#include "qetgraphicsitem/independenttextitem.h"
#include "qetgraphicsitem/qetshapeitem.h"
#include "itemgroups.h"
#include "qetinformation.h"
/*
@@ -833,6 +834,20 @@ void ElementsPanelWidget::duplicateDiagram()
}
}
}
// Groups too: a group of the copy is its own, so selecting it
// is the same on both folios but the database tells them apart.
QHash<QUuid, QUuid> renewed_groups;
for (QGraphicsItem *item : new_diagram->items()) {
const QUuid source_group = ItemGroups::groupOf(item);
if (source_group.isNull()) {
continue;
}
if (!renewed_groups.contains(source_group)) {
renewed_groups.insert(source_group, QUuid::createUuid());
}
new_diagram->setItemGroup(item, renewed_groups.value(source_group));
}
}
elements_panel->reload();
+124
View File
@@ -0,0 +1,124 @@
/*
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 "itemgroups.h"
#include <QDomElement>
#include <QGraphicsItem>
#include <QGraphicsScene>
#include <QSet>
/**
@return the uuid of the group @a item belongs to, or a null uuid.
*/
QUuid ItemGroups::groupOf(const QGraphicsItem *item)
{
return item ? item->data(data_key).toUuid() : QUuid();
}
/**
Put @a item in @a group, or take it out of any group if @a group is null.
*/
void ItemGroups::setGroup(QGraphicsItem *item, const QUuid &group)
{
if (!item) {
return;
}
item->setData(data_key, group.isNull() ? QVariant() : QVariant(group));
}
/**
Write @a item's group, if it has one, on its XML element @a xml.
Nothing is written otherwise, so a project without groups saves exactly
as before.
*/
void ItemGroups::write(QDomElement &xml, const QGraphicsItem *item)
{
const QUuid group = groupOf(item);
if (!group.isNull()) {
xml.setAttribute(QString::fromLatin1(xml_attribute), group.toString());
}
}
/**
@return the group written on @a xml, or a null uuid.
*/
QUuid ItemGroups::read(const QDomElement &xml)
{
return QUuid(xml.attribute(QString::fromLatin1(xml_attribute)));
}
/**
Make the selection of @a scene whole groups again after it changed.
A group with a selected member is selected entirely, except when the
change took members of an entirely selected group out of the selection
while @a toggling (Ctrl+click on a member): then the whole group leaves
the selection, as the user meant.
@param previous : the selection before the change
@param toggling : true when Ctrl is held
@return true if the selection was changed
*/
bool ItemGroups::completeSelection(QGraphicsScene *scene,
const QList<QGraphicsItem *> &previous,
bool toggling)
{
if (!scene) {
return false;
}
QSet<QUuid> touched;
const QList<QGraphicsItem *> selected = scene->selectedItems();
for (QGraphicsItem *item : selected) {
const QUuid group = groupOf(item);
if (!group.isNull()) {
touched << group;
}
}
//Groups that just lost a member from the selection
QSet<QUuid> shrunk;
for (QGraphicsItem *item : previous) {
if (item->scene() == scene && !item->isSelected()) {
const QUuid group = groupOf(item);
if (!group.isNull()) {
shrunk << group;
}
}
}
if (touched.isEmpty()) {
return false;
}
//One pass over the folio for every touched group, not one per group:
//select all on a folio of 2000 items in 200 groups took 44 ms the
//other way.
bool changed = false;
for (QGraphicsItem *item : scene->items())
{
const QUuid group = groupOf(item);
if (group.isNull() || !touched.contains(group)) {
continue;
}
const bool select = !(toggling && shrunk.contains(group));
if (item->isSelected() != select) {
item->setSelected(select);
changed = true;
}
}
return changed;
}
+60
View File
@@ -0,0 +1,60 @@
/*
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 ITEMGROUPS_H
#define ITEMGROUPS_H
#include <QList>
#include <QUuid>
class QDomElement;
class QGraphicsItem;
class QGraphicsScene;
/**
Groups of items on a folio (discussion #1070): symbols, free texts,
shapes and pictures that select, move, copy and delete together.
A group is not an object in the scene. Each member keeps its place and
carries the group's uuid, and selecting one member selects them all;
moving, copying and deleting then need nothing new, because they act on
the selection. Re-parenting the members under a QGraphicsItemGroup would
make every position group-relative, which every piece of code reading a
symbol's position would have to learn about.
This part knows nothing of Diagram, so it can be tested on a plain scene.
*/
namespace ItemGroups
{
/// QGraphicsItem::data() key holding an item's group uuid.
inline constexpr int data_key = 0x475250; // "GRP"
/// XML attribute holding it, on <element>, <input>, <shape> and <image>.
inline constexpr char xml_attribute[] = "group";
QUuid groupOf(const QGraphicsItem *item);
void setGroup(QGraphicsItem *item, const QUuid &group);
void write(QDomElement &xml, const QGraphicsItem *item);
QUuid read(const QDomElement &xml);
bool completeSelection(QGraphicsScene *scene,
const QList<QGraphicsItem *> &previous,
bool toggling);
}
#endif // ITEMGROUPS_H
+27 -2
View File
@@ -66,6 +66,7 @@
#include "undocommand/addelementtextcommand.h"
#include "utils/qetsettings.h"
#include "utils/qetutils.h"
#include "undocommand/groupitemscommand.h"
#include "undocommand/alignselectioncommand.h"
#include "undocommand/rotateselectioncommand.h"
#include "undocommand/rotatetextscommand.h"
@@ -815,6 +816,8 @@ void QETDiagramEditor::setUpActions()
m_find_element = m_selection_actions_group.addAction( QET::Icons::ZoomDraw, tr("Retrouver dans le panel") );
m_edit_selection = m_selection_actions_group.addAction( QET::Icons::ElementEdit, tr("Éditer l'item sélectionné") );
m_group_selected_texts = m_selection_actions_group.addAction( QET::Icons::textGroup, tr("Grouper les textes sélectionnés"));
m_group_selection = m_selection_actions_group.addAction( tr("Grouper") );
m_ungroup_selection = m_selection_actions_group.addAction( tr("Dégrouper") );
ShortcutManager::instance().registerAction(m_delete_selection, "diagrameditor.delete_selection", tr("Éditeur de schémas"), Qt::Key_Delete);
ShortcutManager::instance().registerAction(m_rotate_selection, "diagrameditor.rotate_selection", tr("Éditeur de schémas"), Qt::Key_Space);
@@ -920,6 +923,14 @@ void QETDiagramEditor::setUpActions()
m_find_element ->setData("find_selected_element");
m_edit_selection ->setData("edit_selected_element");
m_group_selected_texts->setData("group_selected_texts");
m_group_selection ->setData("group_selection");
m_ungroup_selection ->setData("ungroup_selection");
//No default key: Ctrl+G is "jump to element". A user can bind one.
ShortcutManager::instance().registerAction(m_group_selection, "diagrameditor.group_selection", tr("Éditeur de schémas"), QKeySequence());
ShortcutManager::instance().registerAction(m_ungroup_selection, "diagrameditor.ungroup_selection", tr("Éditeur de schémas"), QKeySequence());
m_group_selection ->setStatusTip(tr("Groupe les éléments, textes, formes et images sélectionnés : ils se sélectionnent, se déplacent et se copient ensemble", "status bar tip"));
m_ungroup_selection->setStatusTip(tr("Défait les groupes sélectionnés", "status bar tip"));
connect(&m_selection_actions_group, &QActionGroup::triggered, this, &QETDiagramEditor::selectionGroupTriggered);
@@ -2059,7 +2070,7 @@ void QETDiagramEditor::selectionGroupTriggered(QAction *action)
//nothing to rotate.
if (RotateTextsCommand::hasSelectedTexts(diagram))
{
qreal rotation = 0;
qreal rotation = RotateTextsCommand::currentRotation(diagram);
if (RotateTextsCommand::askRotation(rotation))
diagram->undoStack().push(new RotateTextsCommand(diagram, rotation));
}
@@ -2068,6 +2079,15 @@ void QETDiagramEditor::selectionGroupTriggered(QAction *action)
findElementInPanel(currentElement()->location());
else if (value == "edit_selected_element")
dv->editSelection();
else if (value == "group_selection" || value == "ungroup_selection")
{
GroupItemsCommand *command = value == "group_selection"
? GroupItemsCommand::group(diagram)
: GroupItemsCommand::ungroup(diagram);
if (command) {
diagram->undoStack().push(command);
}
}
else if (value == "group_selected_texts")
{
QList<DynamicElementTextItem *> deti_list = dc.m_element_texts.values();
@@ -2250,7 +2270,9 @@ void QETDiagramEditor::slot_updateComplexActions()
<< m_rotate_selection
<< m_rotate_group_selection
<< m_edit_selection
<< m_group_selected_texts;
<< m_group_selected_texts
<< m_group_selection
<< m_ungroup_selection;
for(QAction *action : action_list)
action->setEnabled(false);
m_align_actions_group.setEnabled(false);
@@ -2315,6 +2337,9 @@ void QETDiagramEditor::slot_updateComplexActions()
else
m_group_selected_texts->setDisabled(true);
m_group_selection ->setEnabled(GroupItemsCommand::canGroup(diagram_));
m_ungroup_selection->setEnabled(GroupItemsCommand::canUngroup(diagram_));
// actions need only one editable item
int selected_image = dc.count(DiagramContent::Images);
+2
View File
@@ -256,6 +256,8 @@ class QETDiagramEditor : public QETMainWindow
*m_rotate_texts, ///< Direct selected text items to a specific angle
*m_find_element, ///< Find the selected element in the panel
*m_group_selected_texts = nullptr,
*m_group_selection = nullptr, ///< Group the selected items (#1070)
*m_ungroup_selection = nullptr, ///< Ungroup the selected groups
*m_close_file, ///< Close current project file
*m_save_file, ///< Save current project
*m_save_file_as, ///< Save current project as a specific file
@@ -19,6 +19,7 @@
#include <algorithm>
#include "../../dataBase/legacyelementtypes.h"
#include "../../dataBase/projectdatabase.h"
#include "../../qetapp.h"
#include "../../qetinformation.h"
@@ -294,7 +295,9 @@ void ProjectDBModel::fromXml(const QDomElement &element)
return;
setIdentifier(element.firstChildElement("identifier").text());
setQuery(element.firstChildElement("query").text());
//A query saved before June 2022 names element types the old way
setQuery(LegacyElementTypes::upgradeQuery(
element.firstChildElement("query").text()));
//Index 0,0
auto index_00 = element.firstChildElement("index00");
@@ -70,6 +70,7 @@ GeneralConfigurationPage::GeneralConfigurationPage(QWidget *parent) :
//Stored as "inserts" but presented as "edits", so the default (insert)
//is the unchecked state -- a preference reads better as an opt-out.
ui->m_collection_dblclick_edits->setChecked(!settings.value("elementscollection/double-click-inserts", true).toBool());
ui->m_collection_search_flat_cb->setChecked(settings.value("elementscollection/search-flat-list", true).toBool());
ui->m_context_toolbar_cb->setChecked(settings.value("diagrameditor/context_toolbar", true).toBool());
ui->m_mouse_gestures_cb->setChecked(settings.value("diagrameditor/mouse_gestures", true).toBool());
ui->DiagramEditor_xGrid_sb->setValue(settings.value("diagrameditor/Xgrid", 10).toInt());
@@ -302,6 +303,7 @@ void GeneralConfigurationPage::applyConf()
settings.setValue("diagrameditor/grid_display_startup", ui->grid_startup_cb->isChecked());
settings.setValue("diagrameditor/guides_display_startup", ui->guides_startup_cb->isChecked());
settings.setValue("elementscollection/double-click-inserts", !ui->m_collection_dblclick_edits->isChecked());
settings.setValue("elementscollection/search-flat-list", ui->m_collection_search_flat_cb->isChecked());
settings.setValue("diagrameditor/context_toolbar", ui->m_context_toolbar_cb->isChecked());
settings.setValue("diagrameditor/mouse_gestures", ui->m_mouse_gestures_cb->isChecked());
//Grid step and key navigation
@@ -94,6 +94,16 @@
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="m_collection_search_flat_cb">
<property name="toolTip">
<string>Coché : une seule liste, la meilleure correspondance en premier. Décoché : l'arborescence de la collection, filtrée sur la recherche.</string>
</property>
<property name="text">
<string>Afficher les résultats de recherche sous forme de liste triée</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="m_context_toolbar_cb">
<property name="toolTip">
+181
View File
@@ -0,0 +1,181 @@
/*
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 "groupitemscommand.h"
#include "../diagram.h"
#include "../itemgroups.h"
#include "../qetgraphicsitem/diagramimageitem.h"
#include "../qetgraphicsitem/element.h"
#include "../qetgraphicsitem/independenttextitem.h"
#include "../qetgraphicsitem/qetshapeitem.h"
#include <QSet>
namespace {
QList<QGraphicsObject *> selectedGroupable(Diagram *diagram)
{
QList<QGraphicsObject *> items;
for (QGraphicsItem *item : diagram->selectedItems()) {
if (GroupItemsCommand::isGroupable(item)) {
items << item->toGraphicsObject();
}
}
return items;
}
}
/**
@return a command grouping the selected symbols, free texts, shapes and
pictures of @a diagram into one new group, or nullptr if fewer than two
are selected. The selection already holds whole groups (see
ItemGroups::completeSelection()), so grouping items that were in groups
merges those groups into the new one.
*/
GroupItemsCommand *GroupItemsCommand::group(Diagram *diagram)
{
if (!canGroup(diagram)) {
return nullptr;
}
auto command = new GroupItemsCommand(diagram, selectedGroupable(diagram), QUuid::createUuid());
command->setText(QObject::tr("Grouper %n objet(s)", "", command->m_changes.size()));
return command;
}
/**
@return a command taking every member of the selected groups of
@a diagram out of its group, or nullptr if no group is selected.
*/
GroupItemsCommand *GroupItemsCommand::ungroup(Diagram *diagram)
{
if (!canUngroup(diagram)) {
return nullptr;
}
QList<QGraphicsObject *> items;
for (QGraphicsObject *item : selectedGroupable(diagram)) {
if (!ItemGroups::groupOf(item).isNull()) {
items << item;
}
}
auto command = new GroupItemsCommand(diagram, items, QUuid());
command->setText(QObject::tr("Dégrouper %n objet(s)", "", command->m_changes.size()));
return command;
}
/**
@return true for the kinds of item a group can hold: symbols, free texts,
shapes and pictures. Never conductors, which follow their symbols, and
never a symbol's own texts, which belong to it.
*/
bool GroupItemsCommand::isGroupable(const QGraphicsItem *item)
{
if (!item) {
return false;
}
switch (item->type())
{
case Element::Type:
case IndependentTextItem::Type:
case QetShapeItem::Type:
case DiagramImageItem::Type:
return true;
default:
return false;
}
}
/**
@return true if the selection of @a diagram holds at least two items
that could be grouped, and is not already exactly one group.
*/
bool GroupItemsCommand::canGroup(Diagram *diagram)
{
if (!diagram || diagram->isReadOnly()) {
return false;
}
const QList<QGraphicsObject *> items = selectedGroupable(diagram);
if (items.size() < 2) {
return false;
}
QSet<QUuid> groups;
for (QGraphicsObject *item : items) {
groups << ItemGroups::groupOf(item);
}
return groups.size() > 1 || groups.contains(QUuid());
}
/**
@return true if the selection of @a diagram holds a grouped item.
*/
bool GroupItemsCommand::canUngroup(Diagram *diagram)
{
if (!diagram || diagram->isReadOnly()) {
return false;
}
for (QGraphicsObject *item : selectedGroupable(diagram)) {
if (!ItemGroups::groupOf(item).isNull()) {
return true;
}
}
return false;
}
/**
@brief GroupItemsCommand::GroupItemsCommand
@param diagram : diagram holding @a items
@param items : items to put in @a group
@param group : the new group, or a null uuid to ungroup
*/
GroupItemsCommand::GroupItemsCommand(Diagram *diagram,
const QList<QGraphicsObject *> &items,
const QUuid &group) :
m_diagram(diagram),
m_group(group)
{
for (QGraphicsObject *item : items) {
m_changes.append({item, ItemGroups::groupOf(item)});
}
}
/**
@brief GroupItemsCommand::undo
*/
void GroupItemsCommand::undo()
{
apply(false);
}
/**
@brief GroupItemsCommand::redo
*/
void GroupItemsCommand::redo()
{
apply(true);
}
void GroupItemsCommand::apply(bool redo)
{
if (!m_diagram) {
return;
}
m_diagram->showMe();
for (const Change &change : std::as_const(m_changes)) {
if (change.item) {
m_diagram->setItemGroup(change.item, redo ? m_group : change.before);
}
}
}
+62
View File
@@ -0,0 +1,62 @@
/*
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 GROUPITEMSCOMMAND_H
#define GROUPITEMSCOMMAND_H
#include <QList>
#include <QPointer>
#include <QUndoCommand>
#include <QUuid>
class Diagram;
class QGraphicsItem;
class QGraphicsObject;
/**
@brief The GroupItemsCommand class
Groups or ungroups the selected items of a diagram (discussion #1070).
Undo gives every item back the exact group it had, so a redo after it
still refers to the same groups.
*/
class GroupItemsCommand : public QUndoCommand
{
public:
static GroupItemsCommand *group(Diagram *diagram);
static GroupItemsCommand *ungroup(Diagram *diagram);
static bool isGroupable(const QGraphicsItem *item);
static bool canGroup(Diagram *diagram);
static bool canUngroup(Diagram *diagram);
void undo() override;
void redo() override;
private:
GroupItemsCommand(Diagram *diagram, const QList<QGraphicsObject *> &items, const QUuid &group);
void apply(bool redo);
struct Change {
QPointer<QGraphicsObject> item;
QUuid before;
};
QPointer<Diagram> m_diagram;
QList<Change> m_changes;
QUuid m_group;
};
#endif // GROUPITEMSCOMMAND_H
+34 -2
View File
@@ -25,6 +25,8 @@
#include "../qetgraphicsitem/elementtextitemgroup.h"
#include "../qtextorientationspinboxwidget.h"
#include <cmath>
/**
@brief RotateTextsCommand::hasSelectedTexts
@param diagram
@@ -41,6 +43,35 @@ bool RotateTextsCommand::hasSelectedTexts(Diagram *diagram)
return (!dc.selectedTexts().isEmpty() || !dc.selectedTextsGroup().isEmpty());
}
/**
@brief RotateTextsCommand::currentRotation
@param diagram
@return the rotation shared by every selected text and text group of
@p diagram, so the dialog can open at the angle they already have.
0 when nothing is selected or when their angles differ.
*/
qreal RotateTextsCommand::currentRotation(Diagram *diagram)
{
if(!diagram)
return 0;
DiagramContent dc(diagram);
QList<qreal> angles;
for(DiagramTextItem *dti : dc.selectedTexts())
angles << dti->rotation();
for(ElementTextItemGroup *etig : dc.selectedTextsGroup())
angles << etig->rotation();
if(angles.isEmpty())
return 0;
for(qreal angle : angles)
if(qAbs(angle - angles.first()) > 0.01)
return 0;
//The dialog's spin box accepts -360 to 360
return std::fmod(angles.first(), 360);
}
/**
@brief RotateTextsCommand::RotateTextsCommand
@param diagram : Apply the rotation to the selected texts and group of texts
@@ -124,8 +155,8 @@ void RotateTextsCommand::redo()
/**
@brief RotateTextsCommand::askRotation
Ask the user for an orientation.
@param rotation : set to the chosen angle when the dialog is accepted,
left untouched otherwise.
@param rotation : the angle the dialog opens at; set to the chosen
angle when the dialog is accepted, left untouched otherwise.
@return true if the user accepted, false if they cancelled.
Deliberately static and separate from the command: a QUndoCommand that
@@ -145,6 +176,7 @@ bool RotateTextsCommand::askRotation(qreal &rotation)
QTextOrientationSpinBoxWidget *ori_widget = QETApp::createTextOrientationSpinBoxWidget();
ori_widget->setParent(&ori_text_dialog);
ori_widget->setOrientation(rotation);
ori_widget->spinBox()->selectAll();
QDialogButtonBox buttons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
+4 -2
View File
@@ -40,7 +40,7 @@ class QParallelAnimationGroup;
Typical interactive use:
@code
if (RotateTextsCommand::hasSelectedTexts(diagram)) {
qreal rotation = 0;
qreal rotation = RotateTextsCommand::currentRotation(diagram);
if (RotateTextsCommand::askRotation(rotation))
diagram->undoStack().push(new RotateTextsCommand(diagram, rotation));
}
@@ -53,7 +53,9 @@ class RotateTextsCommand : public QUndoCommand
/// @return true if @p diagram has at least one selected text or text group to rotate.
static bool hasSelectedTexts(Diagram *diagram);
/// Open the orientation dialog. @return true and set @p rotation if accepted, false if cancelled.
/// @return the angle the selected texts share, or 0 if they differ.
static qreal currentRotation(Diagram *diagram);
/// Open the orientation dialog at @p rotation. @return true and set @p rotation if accepted, false if cancelled.
static bool askRotation(qreal &rotation);
void undo() override;
+13
View File
@@ -111,6 +111,19 @@ add_test(NAME tst_alignment COMMAND tst_alignment)
target_include_directories(tst_alignment PRIVATE ${QET_DIR}/sources)
target_link_libraries(tst_alignment PRIVATE Qt::Test)
# itemgroups.cpp keeps group membership and the selection rule apart from
# Diagram, so it is tested here on a plain scene of rectangles.
add_executable(tst_itemgroups tst_itemgroups.cpp ${QET_DIR}/sources/itemgroups.cpp)
add_test(NAME tst_itemgroups COMMAND tst_itemgroups)
target_include_directories(tst_itemgroups PRIVATE ${QET_DIR}/sources)
target_link_libraries(tst_itemgroups PRIVATE Qt::Test Qt::Widgets Qt::Xml)
# legacyelementtypes.h is header-only: the rewrite of element type names in
# nomenclature queries saved before June 2022.
add_executable(tst_legacyelementtypes tst_legacyelementtypes.cpp)
add_test(NAME tst_legacyelementtypes COMMAND tst_legacyelementtypes)
target_include_directories(tst_legacyelementtypes PRIVATE ${QET_DIR}/sources)
target_link_libraries(tst_legacyelementtypes PRIVATE Qt::Test)
add_executable(
tst_qetpalette
tst_qetpalette.cpp
+142
View File
@@ -0,0 +1,142 @@
#include <QtTest>
#include <QDomDocument>
#include <QGraphicsRectItem>
#include <QGraphicsScene>
#include "itemgroups.h"
class tst_itemgroups : public QObject
{
Q_OBJECT
QGraphicsScene *scene = nullptr;
QGraphicsRectItem *a = nullptr, *b = nullptr, *c = nullptr, *d = nullptr, *e = nullptr;
QUuid g1, g2;
QGraphicsRectItem *add()
{
auto item = scene->addRect(0, 0, 10, 10);
item->setFlag(QGraphicsItem::ItemIsSelectable);
return item;
}
QList<QGraphicsItem *> selection() const { return scene->selectedItems(); }
// Select exactly @a items, as Qt leaves the scene after a click.
void select(const QList<QGraphicsItem *> &items)
{
scene->clearSelection();
for (auto item : items) {
item->setSelected(true);
}
}
bool selected(std::initializer_list<QGraphicsItem *> expected) const
{
QList<QGraphicsItem *> got = selection();
if (got.size() != int(expected.size())) {
return false;
}
for (auto item : expected) {
if (!got.contains(item)) {
return false;
}
}
return true;
}
private slots:
// a, b in g1; c alone; d, e in g2
void init()
{
scene = new QGraphicsScene;
a = add(); b = add(); c = add(); d = add(); e = add();
g1 = QUuid::createUuid();
g2 = QUuid::createUuid();
ItemGroups::setGroup(a, g1);
ItemGroups::setGroup(b, g1);
ItemGroups::setGroup(d, g2);
ItemGroups::setGroup(e, g2);
}
void cleanup()
{
delete scene;
scene = nullptr;
}
void clickingAMemberSelectsTheGroup()
{
select({a});
QVERIFY(ItemGroups::completeSelection(scene, {}, false));
QVERIFY(selected({a, b}));
}
// Releasing a click on one member of the selected group leaves only it
// selected in Qt; the group must stay whole.
void reclickingAMemberKeepsTheGroup()
{
select({b});
ItemGroups::completeSelection(scene, {a, b}, false);
QVERIFY(selected({a, b}));
}
void ctrlClickingAMemberOffDeselectsTheGroup()
{
select({b});
ItemGroups::completeSelection(scene, {a, b}, true);
QVERIFY(selected({}));
}
void ctrlClickingAMemberOnAddsTheGroup()
{
select({c, a});
ItemGroups::completeSelection(scene, {c}, true);
QVERIFY(selected({a, b, c}));
}
void otherGroupsAreUntouched()
{
select({b, d, e});
ItemGroups::completeSelection(scene, {a, b, d, e}, true);
QVERIFY(selected({d, e}));
}
void ungroupedItemsAreUntouched()
{
select({c});
QVERIFY(!ItemGroups::completeSelection(scene, {}, false));
QVERIFY(selected({c}));
}
// A member taken off the scene (deleted, kept by the undo stack) must
// not count as "deselected".
void itemsLeavingTheSceneAreIgnored()
{
select({a, b});
scene->removeItem(b);
ItemGroups::completeSelection(scene, {a, b}, true);
QVERIFY(selected({a}));
delete b;
b = nullptr;
}
void xmlRoundTrip()
{
QDomDocument doc;
QDomElement grouped = doc.createElement(QStringLiteral("element"));
ItemGroups::write(grouped, a);
QCOMPARE(ItemGroups::read(grouped), g1);
QDomElement alone = doc.createElement(QStringLiteral("element"));
ItemGroups::write(alone, c);
QVERIFY(!alone.hasAttribute(QStringLiteral("group")));
QVERIFY(ItemGroups::read(alone).isNull());
ItemGroups::setGroup(a, QUuid());
QVERIFY(ItemGroups::groupOf(a).isNull());
}
};
QTEST_MAIN(tst_itemgroups)
#include "tst_itemgroups.moc"
+60
View File
@@ -0,0 +1,60 @@
/*
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 "dataBase/legacyelementtypes.h"
#include <QTest>
class TestLegacyElementTypes : public QObject
{
Q_OBJECT
private slots:
void upgradesOldNames_data()
{
QTest::addColumn<QString>("saved");
QTest::addColumn<QString>("expected");
// The WHERE clause industrial.qet's parts list was saved with.
QTest::newRow("simple, with sub types")
<< "WHERE ( element_type = 'Simple' OR element_sub_type = 'coil') ORDER BY label"
<< "WHERE ( element_type = 'simple' OR element_sub_type = 'coil') ORDER BY label";
QTest::newRow("terminal and master, lengths change")
<< "element_type = 'Terminale' OR element_type = 'Master' OR element_type = 'Simple'"
<< "element_type = 'terminal' OR element_type = 'master' OR element_type = 'simple'";
QTest::newRow("reports and thumbnail")
<< "element_type='NextReport' OR element_type = 'PreviousReport' OR element_type = 'Thumbnail'"
<< "element_type='next_report' OR element_type = 'previous_report' OR element_type = 'thumbnail'";
QTest::newRow("current names unchanged")
<< "element_type = 'simple' OR element_type = 'terminal'"
<< "element_type = 'simple' OR element_type = 'terminal'";
QTest::newRow("the word elsewhere unchanged")
<< "WHERE label = 'Simple' OR designation = 'Master'"
<< "WHERE label = 'Simple' OR designation = 'Master'";
QTest::newRow("empty") << "" << "";
}
void upgradesOldNames()
{
QFETCH(QString, saved);
QFETCH(QString, expected);
QCOMPARE(LegacyElementTypes::upgradeQuery(saved), expected);
}
};
QTEST_APPLESS_MAIN(TestLegacyElementTypes)
#include "tst_legacyelementtypes.moc"