mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-09-28 04:54:13 +02:00
Merge master to resolve conflicts with snap-to-grid (#1073)
Both branches added an undo command, an include and a test target at the same spots; kept both. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -13,7 +13,7 @@
|
||||
"finish-args": [
|
||||
"--socket=wayland",
|
||||
"--socket=fallback-x11",
|
||||
"--device=dri",
|
||||
"--device=all",
|
||||
"--share=ipc",
|
||||
"--share=network",
|
||||
"--socket=cups",
|
||||
@@ -33,6 +33,23 @@
|
||||
"modules": [
|
||||
"tkinter.json",
|
||||
"pypi-dependencies.json",
|
||||
{
|
||||
"name": "hidapi",
|
||||
"buildsystem": "cmake-ninja",
|
||||
"config-opts": [
|
||||
"-DCMAKE_INSTALL_PREFIX=/app",
|
||||
"-DCMAKE_BUILD_TYPE=Release",
|
||||
"-DHIDAPI_BUILD_HIDTEST=OFF",
|
||||
"-DHIDAPI_WITH_TESTS=OFF"
|
||||
],
|
||||
"sources": [
|
||||
{
|
||||
"type": "git",
|
||||
"url": "https://github.com/libusb/hidapi.git",
|
||||
"tag": "hidapi-0.15.0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "qelectrotech",
|
||||
"buildsystem": "cmake",
|
||||
@@ -45,7 +62,9 @@
|
||||
"-DBUILD_PUGIXML=ON",
|
||||
"-DFETCHCONTENT_FULLY_DISCONNECTED=ON",
|
||||
"-DFETCHCONTENT_SOURCE_DIR_PUGIXML=/run/build/qelectrotech/pugixml",
|
||||
"-DQET_EXPORT_PROJECT_DB=ON"
|
||||
"-DQET_EXPORT_PROJECT_DB=ON",
|
||||
"-DQET_ENABLE_SPACEMOUSE=ON",
|
||||
"-DQET_SPACEMOUSE_BACKEND=hid"
|
||||
],
|
||||
"post-install": [
|
||||
"mv ${FLATPAK_DEST}/share/mime/packages/qelectrotech.xml ${FLATPAK_DEST}/share/mime/packages/org.qelectrotech.QElectroTech.xml"
|
||||
|
||||
@@ -840,6 +840,8 @@ set(QET_SRC_FILES
|
||||
${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
|
||||
${QET_DIR}/sources/undocommand/rotateselectioncommand.h
|
||||
${QET_DIR}/sources/undocommand/promoteshapecommand.cpp
|
||||
|
||||
+2968
-1648
File diff suppressed because it is too large
Load Diff
+3200
-1489
File diff suppressed because it is too large
Load Diff
+2954
-1527
File diff suppressed because it is too large
Load Diff
+2915
-1522
File diff suppressed because it is too large
Load Diff
+2801
-1380
File diff suppressed because it is too large
Load Diff
+2915
-1522
File diff suppressed because it is too large
Load Diff
+3075
-1669
File diff suppressed because it is too large
Load Diff
+2896
-1571
File diff suppressed because it is too large
Load Diff
+2813
-1735
File diff suppressed because it is too large
Load Diff
+2860
-1714
File diff suppressed because it is too large
Load Diff
+2867
-1706
File diff suppressed because it is too large
Load Diff
+2773
-1385
File diff suppressed because it is too large
Load Diff
+2903
-1566
File diff suppressed because it is too large
Load Diff
+2873
-1584
File diff suppressed because it is too large
Load Diff
+2900
-1529
File diff suppressed because it is too large
Load Diff
+2879
-1562
File diff suppressed because it is too large
Load Diff
+2900
-1579
File diff suppressed because it is too large
Load Diff
+2926
-1525
File diff suppressed because it is too large
Load Diff
+2798
-1389
File diff suppressed because it is too large
Load Diff
+2777
-1699
File diff suppressed because it is too large
Load Diff
+3228
-1489
File diff suppressed because it is too large
Load Diff
+2842
-1685
File diff suppressed because it is too large
Load Diff
+2799
-1386
File diff suppressed because it is too large
Load Diff
+2878
-1678
File diff suppressed because it is too large
Load Diff
+2862
-1766
File diff suppressed because it is too large
Load Diff
+2943
-1526
File diff suppressed because it is too large
Load Diff
+2838
-1742
File diff suppressed because it is too large
Load Diff
+2859
-1745
File diff suppressed because it is too large
Load Diff
+2838
-1742
File diff suppressed because it is too large
Load Diff
+2927
-1526
File diff suppressed because it is too large
Load Diff
+2897
-1527
File diff suppressed because it is too large
Load Diff
+2943
-1526
File diff suppressed because it is too large
Load Diff
+2773
-1385
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
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 ALIGNMENT_H
|
||||
#define ALIGNMENT_H
|
||||
|
||||
#include "textgrid.h"
|
||||
|
||||
#include <QPointF>
|
||||
|
||||
/**
|
||||
The geometry behind the align commands, kept free of any scene so it
|
||||
can be tested on its own.
|
||||
*/
|
||||
namespace Alignment
|
||||
{
|
||||
/**
|
||||
@return the movement that puts p on a grid of x_grid by y_grid,
|
||||
divided by divisor as TextGrid::snap() does, or a null point when
|
||||
p is already on it. Unlike Diagram::snapToGrid(), this never looks
|
||||
at the keyboard: a command run from a shortcut with Ctrl in it must
|
||||
not quietly round to the pixel instead.
|
||||
Less than a millionth of a pixel counts as on the grid: positions
|
||||
that went through arithmetic carry residues of that size, and
|
||||
qFuzzyIsNull() (1e-12) is too strict to absorb them.
|
||||
*/
|
||||
inline QPointF gridOffset(const QPointF &p, int x_grid, int y_grid, qreal divisor = 1)
|
||||
{
|
||||
const QPointF offset = TextGrid::snap(p, x_grid, y_grid, divisor) - p;
|
||||
auto clean = [](qreal v) { return qAbs(v) < 1e-6 ? 0.0 : v; };
|
||||
return QPointF(clean(offset.x()), clean(offset.y()));
|
||||
}
|
||||
}
|
||||
|
||||
#endif // ALIGNMENT_H
|
||||
+16
-2
@@ -27,6 +27,20 @@ const double Createdxf::sheetWidth = 4000;
|
||||
const double Createdxf::sheetHeight = 2700;
|
||||
|
||||
double Createdxf::xScale = 1;
|
||||
|
||||
namespace {
|
||||
/// @p text on one line. A DXF value ends at the line break, so a break
|
||||
/// inside a text would shift every group code after it by one line and
|
||||
/// a reader would take values for codes (FINDINGS F052). Callers that
|
||||
/// mean several lines split them first; this is the safety net.
|
||||
QString singleLine(QString text)
|
||||
{
|
||||
text.replace(QLatin1String("\r\n"), QLatin1String(" "));
|
||||
text.replace(QLatin1Char('\r'), QLatin1Char(' '));
|
||||
text.replace(QLatin1Char('\n'), QLatin1Char(' '));
|
||||
return text;
|
||||
}
|
||||
}
|
||||
double Createdxf::yScale = 1;
|
||||
|
||||
Createdxf::Createdxf()
|
||||
@@ -727,7 +741,7 @@ void Createdxf::drawText(
|
||||
To_Dxf << 41 << "\r\n";
|
||||
To_Dxf << xScaleW << "\r\n"; // X Scale
|
||||
To_Dxf << 1 << "\r\n";
|
||||
To_Dxf << text << "\r\n"; // Text Value
|
||||
To_Dxf << singleLine(text) << "\r\n"; // Text Value
|
||||
To_Dxf << 50 << "\r\n";
|
||||
To_Dxf << rotation << "\r\n"; // Text Rotation
|
||||
file.close();
|
||||
@@ -780,7 +794,7 @@ void Createdxf::drawTextAligned(
|
||||
To_Dxf << 41 << "\r\n";
|
||||
To_Dxf << xScaleW << "\r\n"; // X Scale
|
||||
To_Dxf << 1 << "\r\n";
|
||||
To_Dxf << text << "\r\n"; // Text Value
|
||||
To_Dxf << singleLine(text) << "\r\n"; // Text Value
|
||||
To_Dxf << 50 << "\r\n";
|
||||
To_Dxf << rotation << "\r\n"; // Text Rotation
|
||||
#if 0
|
||||
|
||||
@@ -1789,6 +1789,7 @@ QList<QAction *> DiagramView::contextMenuActions() const
|
||||
list << qde->m_conductor_reset;
|
||||
list << m_separators.at(1);
|
||||
list << qde->m_selection_actions_group.actions();
|
||||
list << qde->m_align_menu->menuAction();
|
||||
list << m_separators.at(2);
|
||||
list << qde->m_depth_action_group->actions();
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
#include "qetmessagebox.h"
|
||||
|
||||
#include <QGraphicsSimpleTextItem>
|
||||
#include <QSet>
|
||||
#include <QSvgGenerator>
|
||||
#include <QtXml>
|
||||
#include <cmath>
|
||||
@@ -483,7 +484,24 @@ void ExportDialog::generateDxf(
|
||||
// QList <Terminal *> list_terminals;
|
||||
|
||||
// Determine les elements a "XMLiser"
|
||||
foreach(QGraphicsItem *qgi, diagram -> items()) {
|
||||
// In stacking order (z, then insertion order, which a load makes the
|
||||
// file's order), not items() order: that follows memory addresses, so
|
||||
// the same folio came out in a different order on every export, as
|
||||
// saving did before bugtracker #343. A rect query gives stacking order
|
||||
// even with NoIndex; anything it misses keeps its items() place after
|
||||
// the rest.
|
||||
QList<QGraphicsItem *> stacked_items = diagram -> items(
|
||||
QRectF(-1e9, -1e9, 2e9, 2e9), Qt::IntersectsItemBoundingRect,
|
||||
Qt::AscendingOrder);
|
||||
{
|
||||
const QSet<QGraphicsItem *> ranked(stacked_items.cbegin(), stacked_items.cend());
|
||||
for (QGraphicsItem *qgi : diagram -> items()) {
|
||||
if (!ranked.contains(qgi)) {
|
||||
stacked_items << qgi;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (QGraphicsItem *qgi : std::as_const(stacked_items)) {
|
||||
if (Element *elmt = qgraphicsitem_cast<Element *>(qgi)) {
|
||||
list_elements << elmt;
|
||||
} else if (Conductor *f = qgraphicsitem_cast<Conductor *>(qgi)) {
|
||||
|
||||
@@ -67,6 +67,7 @@
|
||||
#include "utils/qetsettings.h"
|
||||
#include "utils/qetutils.h"
|
||||
#include "undocommand/groupitemscommand.h"
|
||||
#include "undocommand/alignselectioncommand.h"
|
||||
#include "undocommand/rotateselectioncommand.h"
|
||||
#include "undocommand/rotatetextscommand.h"
|
||||
#include "diagram.h"
|
||||
@@ -97,6 +98,7 @@ QETDiagramEditor::QETDiagramEditor(const QStringList &files, QWidget *parent) :
|
||||
QETMainWindow(parent),
|
||||
m_row_column_actions_group (this),
|
||||
m_selection_actions_group (this),
|
||||
m_align_actions_group (this),
|
||||
m_add_item_actions_group (this),
|
||||
m_zoom_actions_group (this),
|
||||
m_select_actions_group (this),
|
||||
@@ -932,6 +934,15 @@ void QETDiagramEditor::setUpActions()
|
||||
|
||||
connect(&m_selection_actions_group, &QActionGroup::triggered, this, &QETDiagramEditor::selectionGroupTriggered);
|
||||
|
||||
//Align actions. No default shortcut: they are reached from the
|
||||
//Edit menu, the selection's context menu and the command search,
|
||||
//and a user can bind one if they want.
|
||||
QAction *snap_to_grid = m_align_actions_group.addAction(tr("Aligner sur la grille"));
|
||||
ShortcutManager::instance().registerAction(snap_to_grid, "diagrameditor.snap_selection_to_grid", tr("Éditeur de schémas"), QKeySequence());
|
||||
snap_to_grid->setStatusTip(tr("Remet les éléments, images et textes sélectionnés sur la grille", "status bar tip"));
|
||||
snap_to_grid->setData("snap_selection_to_grid");
|
||||
connect(&m_align_actions_group, &QActionGroup::triggered, this, &QETDiagramEditor::alignGroupTriggered);
|
||||
|
||||
//Select Action
|
||||
QAction *select_all = m_select_actions_group.addAction( QET::Icons::EditSelectAll, tr("Tout sélectionner") );
|
||||
QAction *select_nothing = m_select_actions_group.addAction( QET::Icons::EditSelectNone, tr("Désélectionner tout") );
|
||||
@@ -1226,6 +1237,8 @@ void QETDiagramEditor::setUpMenu()
|
||||
menu_edition -> addActions(m_select_actions_group.actions());
|
||||
menu_edition -> addSeparator();
|
||||
menu_edition -> addActions(m_selection_actions_group.actions());
|
||||
m_align_menu = menu_edition -> addMenu(tr("Aligner"));
|
||||
m_align_menu -> addActions(m_align_actions_group.actions());
|
||||
menu_edition -> addSeparator();
|
||||
menu_edition -> addAction(m_conductor_reset);
|
||||
menu_edition -> addSeparator();
|
||||
@@ -2085,6 +2098,45 @@ void QETDiagramEditor::selectionGroupTriggered(QAction *action)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief QETDiagramEditor::alignGroupTriggered
|
||||
Run the align action @a action on the selection of the current diagram,
|
||||
and say in the status bar what it did, including when there was nothing
|
||||
to do or when locked items were left in place.
|
||||
@param action
|
||||
*/
|
||||
void QETDiagramEditor::alignGroupTriggered(QAction *action)
|
||||
{
|
||||
DiagramView *dv = currentDiagramView();
|
||||
if (!dv || action->data().toString() != QLatin1String("snap_selection_to_grid"))
|
||||
return;
|
||||
|
||||
Diagram *diagram = dv->diagram();
|
||||
auto *command = new AlignSelectionCommand(diagram, AlignSelectionCommand::SnapToGrid);
|
||||
const int locked = command->lockedCount();
|
||||
|
||||
QString message;
|
||||
if (command->isValid())
|
||||
{
|
||||
message = tr("%n objet(s) remis sur la grille", "", command->movedCount());
|
||||
diagram->undoStack().push(command);
|
||||
}
|
||||
else
|
||||
{
|
||||
message = tr("La sélection est déjà sur la grille");
|
||||
delete command;
|
||||
}
|
||||
if (locked)
|
||||
message += QLatin1Char(' ') + tr("(%n objet(s) verrouillé(s) laissé(s) en place)", "", locked);
|
||||
|
||||
//Queued, not shown at once: when the action comes from a menu or the
|
||||
//command search, closing it queues events that would clear a message
|
||||
//shown right now.
|
||||
QTimer::singleShot(0, this, [this, message]() {
|
||||
statusBar()->showMessage(message, 5000);
|
||||
});
|
||||
}
|
||||
|
||||
void QETDiagramEditor::rowColumnGroupTriggered(QAction *action)
|
||||
{
|
||||
QString value = action->data().toString();
|
||||
@@ -2223,6 +2275,7 @@ void QETDiagramEditor::slot_updateComplexActions()
|
||||
<< m_ungroup_selection;
|
||||
for(QAction *action : action_list)
|
||||
action->setEnabled(false);
|
||||
m_align_actions_group.setEnabled(false);
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -2347,6 +2400,11 @@ void QETDiagramEditor::slot_updateComplexActions()
|
||||
| DiagramContent::Shapes
|
||||
| DiagramContent::Images);
|
||||
m_depth_action_group->setEnabled(list.isEmpty()? false : true);
|
||||
|
||||
//Align actions: symbols, pictures and free texts take part
|
||||
m_align_actions_group.setEnabled(!ro && (selected_elements_count
|
||||
|| selected_image
|
||||
|| dc.count(DiagramContent::TextFields)));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -122,6 +122,7 @@ class QETDiagramEditor : public QETMainWindow
|
||||
void selectGroupTriggered (QAction *action);
|
||||
void addItemGroupTriggered (QAction *action);
|
||||
void selectionGroupTriggered (QAction *action);
|
||||
void alignGroupTriggered (QAction *action);
|
||||
void rowColumnGroupTriggered (QAction *action);
|
||||
void slot_updateActions();
|
||||
void slot_updateUndoStack();
|
||||
@@ -189,10 +190,12 @@ class QETDiagramEditor : public QETMainWindow
|
||||
QActionGroup
|
||||
m_row_column_actions_group, /// Action related to add/remove rows/column in diagram
|
||||
m_selection_actions_group, ///Action related to edit a selected item
|
||||
m_align_actions_group, ///Action related to align the selected items
|
||||
*m_depth_action_group = nullptr;
|
||||
|
||||
QMenu
|
||||
*m_add_item_menu = nullptr, ///< Submenu of m_add_item_actions_group
|
||||
*m_align_menu = nullptr, ///< Submenu of m_align_actions_group
|
||||
*m_row_column_menu = nullptr; ///< Submenu of m_row_column_actions_group
|
||||
|
||||
private:
|
||||
|
||||
@@ -1993,32 +1993,58 @@ void TitleBlockTemplate::renderTextCellDxf(
|
||||
}
|
||||
|
||||
//painter.setFont(text_font);
|
||||
qreal ratio = 1.0;
|
||||
|
||||
if (cell.hadjust)
|
||||
//A cell can hold several lines, which the screen shows one under the
|
||||
//other (QPainter::drawText()). A DXF TEXT is a single line, and a line
|
||||
//break inside one breaks the file (FINDINGS F052), so each line gets
|
||||
//its own, stacked as the cell's vertical alignment says: the first at
|
||||
//y1 when aligned to the top, the last at y1 when aligned to the
|
||||
//bottom, the block centred on y1 otherwise. Spacing as the diagram
|
||||
//texts' DXF export: 1.6 times the text height. A single line is
|
||||
//written exactly as before.
|
||||
const QStringList lines = text.split(QRegularExpression(QStringLiteral("\r\n|\r|\n")));
|
||||
const qreal line_spacing = textHeight * Createdxf::yScale * 1.6;
|
||||
const int last = lines.size() - 1;
|
||||
for (int i = 0 ; i < lines.size() ; ++i)
|
||||
{
|
||||
// Scale font width to fit string in cell width w
|
||||
// As DXF font aspect ratio is implementation dependent we add a fudge-factor based on tests with AutoCAD
|
||||
int len = text.length() * textHeight * Createdxf::xScale * 1.2;
|
||||
const QString &line = lines.at(i);
|
||||
if (line.isEmpty()) continue;
|
||||
|
||||
if(len > w)
|
||||
ratio = (w/len);
|
||||
qreal offset; // upward, from y1
|
||||
if (vAlign == 3)
|
||||
offset = -i * line_spacing;
|
||||
else if (vAlign == 2)
|
||||
offset = (last / 2.0 - i) * line_spacing;
|
||||
else
|
||||
offset = (last - i) * line_spacing;
|
||||
|
||||
qreal ratio = 1.0;
|
||||
|
||||
if (cell.hadjust)
|
||||
{
|
||||
// Scale font width to fit string in cell width w
|
||||
// As DXF font aspect ratio is implementation dependent we add a fudge-factor based on tests with AutoCAD
|
||||
int len = line.length() * textHeight * Createdxf::xScale * 1.2;
|
||||
|
||||
if(len > w)
|
||||
ratio = (w/len);
|
||||
}
|
||||
|
||||
// x offset value below currently set heuristically based on appearance...
|
||||
Createdxf::drawTextAligned(
|
||||
file_path,
|
||||
line,
|
||||
x - 2*Createdxf::xScale,
|
||||
y1 + offset,
|
||||
textHeight*Createdxf::yScale,
|
||||
0,
|
||||
0,
|
||||
hAlign,
|
||||
vAlign,
|
||||
x2,
|
||||
ratio,
|
||||
color);
|
||||
}
|
||||
|
||||
// x offset value below currently set heuristically based on appearance...
|
||||
Createdxf::drawTextAligned(
|
||||
file_path,
|
||||
text,
|
||||
x - 2*Createdxf::xScale,
|
||||
y1,
|
||||
textHeight*Createdxf::yScale,
|
||||
0,
|
||||
0,
|
||||
hAlign,
|
||||
vAlign,
|
||||
x2,
|
||||
ratio,
|
||||
color);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
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 "alignselectioncommand.h"
|
||||
|
||||
#include "../QPropertyUndoCommand/qpropertyundocommand.h"
|
||||
#include "../alignment.h"
|
||||
#include "../diagram.h"
|
||||
#include "../diagramcontent.h"
|
||||
#include "../qetgraphicsitem/diagramimageitem.h"
|
||||
#include "../qetgraphicsitem/element.h"
|
||||
#include "../qetgraphicsitem/independenttextitem.h"
|
||||
|
||||
#include <QSettings>
|
||||
|
||||
/**
|
||||
@brief AlignSelectionCommand::AlignSelectionCommand
|
||||
Works out the movement of every selected item for @a mode. Nothing
|
||||
moves until the command is pushed.
|
||||
Elements redraw their conductors themselves when their position
|
||||
changes (see Element's constructor), so a plain "pos" property undo
|
||||
per item is enough.
|
||||
@param diagram : diagram whose selection is aligned
|
||||
@param mode : what to align the selection to
|
||||
@param parent : parent undo command
|
||||
*/
|
||||
AlignSelectionCommand::AlignSelectionCommand(Diagram *diagram, Mode mode, QUndoCommand *parent) :
|
||||
QUndoCommand(parent),
|
||||
m_diagram(diagram)
|
||||
{
|
||||
Q_UNUSED(mode)
|
||||
|
||||
DiagramContent dc(diagram);
|
||||
m_locked_count = dc.removeNonMovableItems();
|
||||
|
||||
QSettings settings;
|
||||
const int x_grid = settings.value(QStringLiteral("diagrameditor/Xgrid"), Diagram::xGrid).toInt();
|
||||
const int y_grid = settings.value(QStringLiteral("diagrameditor/Ygrid"), Diagram::yGrid).toInt();
|
||||
const qreal text_divisor = settings.value(TextGrid::settings_key, 1).toReal();
|
||||
|
||||
auto move = [this](QGraphicsObject *item, const QPointF &offset)
|
||||
{
|
||||
if (!offset.isNull())
|
||||
new QPropertyUndoCommand(item, "pos", item->pos(), item->pos() + offset, this);
|
||||
};
|
||||
|
||||
//Each kind goes where dragging it would have left it: symbols and
|
||||
//pictures on the folio grid, free texts on the text grid.
|
||||
//Shapes are left out: they are made of several points and no single
|
||||
//one of them is the obvious one to snap.
|
||||
for (Element *element : std::as_const(dc.m_elements))
|
||||
move(element, Alignment::gridOffset(element->pos(), x_grid, y_grid));
|
||||
for (DiagramImageItem *image : std::as_const(dc.m_images))
|
||||
move(image, Alignment::gridOffset(image->pos(), x_grid, y_grid));
|
||||
for (IndependentTextItem *text : std::as_const(dc.m_text_fields))
|
||||
move(text, Alignment::gridOffset(text->pos(), x_grid, y_grid, text_divisor));
|
||||
|
||||
setText(QObject::tr("Aligner %n objet(s) sur la grille", "", childCount()));
|
||||
}
|
||||
|
||||
/**
|
||||
@brief AlignSelectionCommand::undo
|
||||
*/
|
||||
void AlignSelectionCommand::undo()
|
||||
{
|
||||
if (m_diagram)
|
||||
m_diagram->showMe();
|
||||
QUndoCommand::undo();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief AlignSelectionCommand::redo
|
||||
*/
|
||||
void AlignSelectionCommand::redo()
|
||||
{
|
||||
if (m_diagram)
|
||||
m_diagram->showMe();
|
||||
QUndoCommand::redo();
|
||||
}
|
||||
|
||||
/**
|
||||
@brief AlignSelectionCommand::isValid
|
||||
@return true if this command moves at least one item.
|
||||
*/
|
||||
bool AlignSelectionCommand::isValid() const
|
||||
{
|
||||
return childCount() > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@return the number of items this command moves.
|
||||
*/
|
||||
int AlignSelectionCommand::movedCount() const
|
||||
{
|
||||
return childCount();
|
||||
}
|
||||
|
||||
/**
|
||||
@return the number of selected items left in place because their
|
||||
position is locked.
|
||||
*/
|
||||
int AlignSelectionCommand::lockedCount() const
|
||||
{
|
||||
return m_locked_count;
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
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 ALIGNSELECTIONCOMMAND_H
|
||||
#define ALIGNSELECTIONCOMMAND_H
|
||||
|
||||
#include <QPointer>
|
||||
#include <QUndoCommand>
|
||||
|
||||
class Diagram;
|
||||
|
||||
/**
|
||||
@brief The AlignSelectionCommand class
|
||||
Moves each selected item by its own amount, as one undo step.
|
||||
Symbols, pictures and free texts take part; locked items are left
|
||||
where they are and counted, so the caller can say so.
|
||||
*/
|
||||
class AlignSelectionCommand : public QUndoCommand
|
||||
{
|
||||
public:
|
||||
enum Mode {
|
||||
SnapToGrid ///< put each item where a drag would have left it
|
||||
};
|
||||
|
||||
AlignSelectionCommand(Diagram *diagram, Mode mode, QUndoCommand *parent = nullptr);
|
||||
|
||||
void undo() override;
|
||||
void redo() override;
|
||||
|
||||
bool isValid() const;
|
||||
int movedCount() const;
|
||||
int lockedCount() const;
|
||||
|
||||
private:
|
||||
QPointer<Diagram> m_diagram;
|
||||
int m_locked_count = 0;
|
||||
};
|
||||
|
||||
#endif // ALIGNSELECTIONCOMMAND_H
|
||||
@@ -104,6 +104,13 @@ add_test(NAME tst_textgrid COMMAND tst_textgrid)
|
||||
target_include_directories(tst_textgrid PRIVATE ${QET_DIR}/sources)
|
||||
target_link_libraries(tst_textgrid PRIVATE Qt::Test)
|
||||
|
||||
# alignment.h is header-only too (it builds on textgrid.h): the geometry
|
||||
# behind the align commands, tested without a scene.
|
||||
add_executable(tst_alignment tst_alignment.cpp)
|
||||
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)
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
#include <QtTest>
|
||||
|
||||
#include "alignment.h"
|
||||
|
||||
class tst_alignment : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private slots:
|
||||
void gridOffset_data()
|
||||
{
|
||||
QTest::addColumn<QPointF>("pos");
|
||||
QTest::addColumn<int>("x_grid");
|
||||
QTest::addColumn<int>("y_grid");
|
||||
QTest::addColumn<qreal>("divisor");
|
||||
QTest::addColumn<QPointF>("expected");
|
||||
|
||||
QTest::newRow("on the grid") << QPointF(100, 50) << 10 << 10 << 1.0 << QPointF(0, 0);
|
||||
QTest::newRow("5 px right, fine nudge") << QPointF(105, 50) << 10 << 10 << 1.0 << QPointF(5, 0);
|
||||
QTest::newRow("either side") << QPointF(103, 97) << 10 << 10 << 1.0 << QPointF(-3, 3);
|
||||
QTest::newRow("negative, fraction") << QPointF(-13.3, 13.7) << 10 << 10 << 1.0 << QPointF(3.3, -3.7);
|
||||
QTest::newRow("uneven grid") << QPointF(12, 13) << 10 << 5 << 1.0 << QPointF(-2, 2);
|
||||
QTest::newRow("text grid 1:2") << QPointF(103, 97) << 10 << 10 << 2.0 << QPointF(2, -2);
|
||||
QTest::newRow("text grid off") << QPointF(103.4, 96.6) << 10 << 10 << 0.0 << QPointF(-0.4, 0.4);
|
||||
}
|
||||
|
||||
void gridOffset()
|
||||
{
|
||||
QFETCH(QPointF, pos);
|
||||
QFETCH(int, x_grid);
|
||||
QFETCH(int, y_grid);
|
||||
QFETCH(qreal, divisor);
|
||||
QFETCH(QPointF, expected);
|
||||
|
||||
const QPointF offset = Alignment::gridOffset(pos, x_grid, y_grid, divisor);
|
||||
QVERIFY2(qAbs(offset.x() - expected.x()) < 1e-9, qPrintable(QString::number(offset.x())));
|
||||
QVERIFY2(qAbs(offset.y() - expected.y()) < 1e-9, qPrintable(QString::number(offset.y())));
|
||||
QVERIFY(qAbs(pos.x() + offset.x() - TextGrid::snap(pos, x_grid, y_grid, divisor).x()) < 1e-9);
|
||||
}
|
||||
|
||||
// An item already on the grid must give an exactly null offset, not a
|
||||
// rounding residue: the command pushes no undo step only when every
|
||||
// offset is null.
|
||||
void onGridIsExactlyNull()
|
||||
{
|
||||
for (int k = -50; k <= 50; ++k) {
|
||||
const QPointF on_grid(k * 10, -k * 10);
|
||||
QVERIFY(Alignment::gridOffset(on_grid, 10, 10).isNull());
|
||||
QVERIFY(Alignment::gridOffset(on_grid + QPointF(1e-10, -1e-10), 10, 10).isNull());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
QTEST_APPLESS_MAIN(tst_alignment)
|
||||
#include "tst_alignment.moc"
|
||||
Reference in New Issue
Block a user