Merge branch 'master' into qt6_cmake_joshua

This commit is contained in:
joshua
2026-07-31 21:15:21 +02:00
parent f16cf7dac8
commit e6d29cf345
297 changed files with 82691 additions and 27463 deletions
+52 -12
View File
@@ -31,6 +31,7 @@
#include "graphicspart/partline.h"
#include "graphicspart/partpolygon.h"
#include "graphicspart/partrectangle.h"
#include "graphicspart/partplctable.h"
#include "graphicspart/partterminal.h"
#include "graphicspart/parttext.h"
#include "ui/qetelementeditor.h"
@@ -84,12 +85,15 @@ ElementData ElementScene::elementData() {
void ElementScene::setElementData(ElementData data)
{
bool emit_ = m_element_data.m_informations != data.m_informations;
bool emit_info = (m_element_data != data);
bool type_changed = m_element_data.m_type != data.m_type;
m_element_data = data;
if (emit_)
if (emit_info)
emit elementInfoChanged();
if (type_changed)
emit elementTypeChanged();
}
/**
@@ -107,6 +111,9 @@ ElementScene::~ElementScene()
if (m_decorator)
delete m_decorator;
if (m_paste_area && !m_paste_area->scene())
delete m_paste_area;
}
/**
@@ -748,7 +755,7 @@ void ElementScene::addItems(QVector<QGraphicsItem *> items)
*/
void ElementScene::removeItems(QVector<QGraphicsItem *> items)
{
const int previous_selected_count{selectedItems().size()};
const int previous_selected_count = static_cast<int>(selectedItems().size());
//block signal to avoid multiple emit of selection changed,
//we emit this signal only once at the end of this function.
@@ -920,9 +927,41 @@ void ElementScene::slot_editProperties()
if (m_element_data != epew.editedData())
{
ElementData new_data = epew.editedData();
// Check PLC state BEFORE pushing (push calls redo which changes m_element_data)
bool old_plc = (m_element_data.m_type == ElementData::Master &&
m_element_data.m_master_type == ElementData::PLC);
bool new_plc = (new_data.m_type == ElementData::Master &&
new_data.m_master_type == ElementData::PLC);
undoStack().push(new changeElementDataCommand(this,
m_element_data,
epew.editedData()));
new_data));
if (new_plc && !old_plc) {
// Switched TO PLC: create table if not present
bool has_plc = false;
for (QGraphicsItem *item : items()) {
if (dynamic_cast<PartPlcTable *>(item)) {
has_plc = true;
break;
}
}
if (!has_plc) {
PartPlcTable *pt = new PartPlcTable(m_element_editor);
addItem(pt);
}
} else if (!new_plc && old_plc) {
// Switched FROM PLC: remove table
for (QGraphicsItem *item : items()) {
if (PartPlcTable *pt = dynamic_cast<PartPlcTable *>(item)) {
removeItem(pt);
delete pt;
break;
}
}
}
}
}
@@ -1166,15 +1205,16 @@ ElementContent ElementScene::loadContent(const QDomDocument &xml_document)
CustomElementPart *cep = nullptr;
PartDynamicTextField *pdtf = nullptr;
if (qde.tagName() == "line") cep = new PartLine (m_element_editor);
else if (qde.tagName() == "rect") cep = new PartRectangle(m_element_editor);
else if (qde.tagName() == "ellipse") cep = new PartEllipse (m_element_editor);
else if (qde.tagName() == "circle") cep = new PartEllipse (m_element_editor);
else if (qde.tagName() == "polygon") cep = new PartPolygon (m_element_editor);
else if (qde.tagName() == "terminal") cep = new PartTerminal (m_element_editor);
else if (qde.tagName() == "text") cep = new PartText (m_element_editor);
else if (qde.tagName() == "arc") cep = new PartArc (m_element_editor);
if (qde.tagName() == "line") cep = new PartLine (m_element_editor);
else if (qde.tagName() == "rect") cep = new PartRectangle (m_element_editor);
else if (qde.tagName() == "ellipse") cep = new PartEllipse (m_element_editor);
else if (qde.tagName() == "circle") cep = new PartEllipse (m_element_editor);
else if (qde.tagName() == "polygon") cep = new PartPolygon (m_element_editor);
else if (qde.tagName() == "terminal") cep = new PartTerminal (m_element_editor);
else if (qde.tagName() == "text") cep = new PartText (m_element_editor);
else if (qde.tagName() == "arc") cep = new PartArc (m_element_editor);
else if (qde.tagName() == "dynamic_text") cep = new PartDynamicTextField (m_element_editor);
else if (qde.tagName() == "plc_table") cep = new PartPlcTable (m_element_editor);
//For the input (aka the old text field) we try to convert it to the new partDynamicTextField
else if (qde.tagName() == "input") cep = pdtf = new PartDynamicTextField(m_element_editor);
else continue;
+2
View File
@@ -177,6 +177,8 @@ class ElementScene : public QGraphicsScene
/// Signal emitted when need zoomFit
void needZoomFit();
void elementInfoChanged();
/// Signal emitted when the element type changes
void elementTypeChanged();
};
Q_DECLARE_OPERATORS_FOR_FLAGS(ElementScene::ItemOptions)
@@ -20,6 +20,7 @@
#include "../../QPropertyUndoCommand/qpropertyundocommand.h"
#include "../elementscene.h"
#include <QApplication>
#include <QRegularExpression>
/**
@@ -39,7 +40,8 @@ CustomElementGraphicPart::CustomElementGraphicPart(QETElementEditor *editor,
_lineweight(NormalWeight),
_filling(NoneFilling),
_color(BlackColor),
_antialiased(false)
_antialiased(false),
m_first_move (false)
{
setFlags(QGraphicsItem::ItemIsSelectable
| QGraphicsItem::ItemIsMovable
@@ -1325,26 +1327,24 @@ void CustomElementGraphicPart::mousePressEvent(QGraphicsSceneMouseEvent *event)
void CustomElementGraphicPart::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
//m_first_move is used to avoid an unwanted behavior
//when the properties dock widget is displayed :
//1 there is no selection
//2 the dock widget width is set to minimum
//3 select a part, the dock widget gain new widgets used to edit
//the current selected part and the width of the dock grow
//so the width of the QGraphicsView is reduced and cause a mouse move event.
//When this case occur the part is moved but they should not. This bool fix it.
if (Q_UNLIKELY(m_first_move)) {
if (m_first_move) {
// Suppress spurious move events fired when the properties dock
// widget expands on first selection of a new item type, causing
// the QGraphicsView to shrink and re-map coordinates. Screen
// coordinates are stable across viewport changes; scene coords
// are not — so use screenPos() for the threshold check.
const QPointF d = event->screenPos() - event->buttonDownScreenPos(Qt::LeftButton);
if (d.manhattanLength() < QApplication::startDragDistance())
return;
m_first_move = false;
return;
}
if((event->buttons() & Qt::LeftButton) && (flags() & QGraphicsItem::ItemIsMovable))
{
if ((event->buttons() & Qt::LeftButton) && (flags() & QGraphicsItem::ItemIsMovable)) {
QPointF pos = event->scenePos() + (m_origin_pos - event->buttonDownScenePos(Qt::LeftButton));
event->modifiers() == Qt::ControlModifier ? setPos(pos) : setPos(elementScene()->snapToGrid(pos));
}
else
} else {
QGraphicsObject::mouseMoveEvent(event);
}
}
void CustomElementGraphicPart::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
@@ -20,6 +20,8 @@
#include "../../QPropertyUndoCommand/qpropertyundocommand.h"
#include "../../qetapp.h"
#include "../elementscene.h"
#include "../../utils/qetutils.h"
#include <QApplication>
#include <QColor>
#include <QFont>
@@ -141,7 +143,7 @@ const QDomElement PartDynamicTextField::toXml(QDomDocument &dom_doc) const
root_element.setAttribute("y", QString::number(y));
root_element.setAttribute("z", QString::number(zValue()));
root_element.setAttribute("rotation", QString::number(QET::correctAngle(rot)));
root_element.setAttribute("font", font().toString());
root_element.setAttribute("font", QETUtils::fontToString(font()));
root_element.setAttribute("uuid", m_uuid.toString());
root_element.setAttribute("frame", m_frame? "true" : "false");
root_element.setAttribute("text_width", QString::number(m_text_width));
@@ -213,7 +215,7 @@ void PartDynamicTextField::fromXml(const QDomElement &dom_elmt) {
if (dom_elmt.hasAttribute("font")) {
QFont font_;
font_.fromString(dom_elmt.attribute("font"));
QETUtils::fontFromString(font_, dom_elmt.attribute("font"));
setFont(font_);
}
else if (dom_elmt.hasAttribute("font_size")) {
@@ -495,12 +497,16 @@ bool PartDynamicTextField::keepVisualRotation() const {
@param event
*/
void PartDynamicTextField::mouseMoveEvent(QGraphicsSceneMouseEvent *event) {
if((event -> buttons() & Qt::LeftButton) && (flags() & QGraphicsItem::ItemIsMovable)) {
QPointF pos = event -> scenePos() + (m_origin_pos - event -> buttonDownScenePos(Qt::LeftButton));
event -> modifiers() == Qt::ControlModifier ? setPos(pos) : setPos(elementScene() -> snapToGrid(pos));
}
else
if ((event->buttons() & Qt::LeftButton) && (flags() & QGraphicsItem::ItemIsMovable)) {
// Suppress spurious moves from the properties dock resizing the viewport.
const QPointF d = event->screenPos() - event->buttonDownScreenPos(Qt::LeftButton);
if (d.manhattanLength() < QApplication::startDragDistance())
return;
QPointF pos = event->scenePos() + (m_origin_pos - event->buttonDownScenePos(Qt::LeftButton));
event->modifiers() == Qt::ControlModifier ? setPos(pos) : setPos(elementScene()->snapToGrid(pos));
} else {
QGraphicsObject::mouseMoveEvent(event);
}
}
/**
@@ -0,0 +1,696 @@
/*
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 "partplctable.h"
#include "../../QPropertyUndoCommand/qpropertyundocommand.h"
#include "../../QetGraphicsItemModeler/qetgraphicshandleritem.h"
#include "../../QetGraphicsItemModeler/qetgraphicshandlerutility.h"
#include "../../properties/elementdata.h"
#include "../elementscene.h"
#include "../editorcommands.h"
#include "../ui/qetelementeditor.h"
#include <QPen>
#include <algorithm>
/**
@brief PartPlcTable::PartPlcTable
Constructor
@param editor the QETElementEditor of this item
@param parent parent item
*/
PartPlcTable::PartPlcTable(QETElementEditor *editor, QGraphicsItem *parent) :
CustomElementGraphicPart(editor, parent)
{
}
/**
@brief PartPlcTable::~PartPlcTable
*/
PartPlcTable::~PartPlcTable()
{
removeHandler();
}
/**
@brief PartPlcTable::calculateTableSize
Calculate the table size from PLC data in the element scene.
@return the calculated size in item coordinates
*/
QSizeF PartPlcTable::calculateTableSize() const
{
if (!elementEditor() || !elementScene())
return QSizeF(100, 50);
ElementData ed = elementScene()->elementData();
if (ed.m_master_type != ElementData::PLC)
return QSizeF(100, 50);
const auto &plc_data = ed.plcMasterData();
if (plc_data.ios.isEmpty())
return QSizeF(100, 50);
const int COL_COUNT = 5;
// Build list of visible columns
QList<int> visible_cols;
if (!plc_data.columnOrder.isEmpty()) {
for (int logical : plc_data.columnOrder) {
if (logical >= 0 && logical < COL_COUNT
&& plc_data.colVisible.value(logical, true)
&& !visible_cols.contains(logical))
visible_cols.append(logical);
}
for (int i = 0; i < COL_COUNT; ++i) {
if (plc_data.colVisible.value(i, true) && !visible_cols.contains(i))
visible_cols.append(i);
}
} else {
for (int i = 0; i < COL_COUNT; ++i) {
if (plc_data.colVisible.value(i, true))
visible_cols.append(i);
}
}
if (visible_cols.isEmpty())
visible_cols << 0 << 1 << 2; // fallback: Type, Address, Function
// Default column widths
QMap<int, qreal> col_widths;
for (int col : visible_cols) {
if (plc_data.colWidths.contains(col) && plc_data.colWidths[col] > 0)
col_widths[col] = plc_data.colWidths[col];
else {
switch (col) {
case 0: col_widths[col] = 35; break; // Type
case 1: col_widths[col] = 25; break; // Address
case 2: col_widths[col] = 50; break; // Function
case 3: col_widths[col] = 40; break; // Comment
case 5: col_widths[col] = 30; break; // CrossRef
default: col_widths[col] = 30; break;
}
}
}
qreal row_h = plc_data.rowHeight > 0 ? plc_data.rowHeight : 8.0;
qreal header_h = plc_data.showHeaders ? (row_h + 2.0) : 0;
qreal total_width = 0;
for (int col : visible_cols)
total_width += col_widths[col];
int total_ios = plc_data.ios.size();
// Collect active break positions (sorted)
QList<int> breaks;
for (int bp : plc_data.breakPositions) {
if (bp > 0 && bp < total_ios && !breaks.contains(bp))
breaks.append(bp);
}
std::sort(breaks.begin(), breaks.end());
// Build block boundaries
QList<int> block_starts;
block_starts.append(0);
for (int bp : breaks)
block_starts.append(bp);
qreal total_height;
int block_count = block_starts.size();
if (block_count > 1) {
// Find the tallest block (most rows)
int max_rows = 0;
for (int b = 0; b < block_count; ++b) {
int start = block_starts.at(b);
int end = (b + 1 < block_starts.size()) ? block_starts.at(b + 1) : total_ios;
max_rows = qMax(max_rows, end - start);
}
total_height = header_h + max_rows * row_h;
total_width = total_width * block_count + (block_count - 1) * 3;
} else {
total_height = header_h + total_ios * row_h;
}
return QSizeF(total_width, total_height);
}
/**
@brief PartPlcTable::paint
Draw this PLC table
@param painter
@param options
@param widget
*/
void PartPlcTable::paint(QPainter *painter, const QStyleOptionGraphicsItem *options, QWidget *widget)
{
Q_UNUSED(widget);
Q_UNUSED(options);
// Auto-size from PLC data
QSizeF table_size = calculateTableSize();
if (m_rect.size() != table_size) {
prepareGeometryChange();
QPointF top_left = m_rect.topLeft();
m_rect = QRectF(top_left, table_size);
}
applyStylesToQPainter(*painter);
QPen t = painter->pen();
t.setCosmetic(options && options->levelOfDetailFromTransform(painter->worldTransform()) < 1.0);
if (isSelected())
t.setColor(Qt::red);
t.setJoinStyle(Qt::MiterJoin);
if (!m_rect.width() || !m_rect.height())
t.setWidth(0);
painter->setPen(t);
// Get PLC data
ElementData ed = (elementEditor() && elementScene()) ? elementScene()->elementData() : ElementData();
if (ed.m_master_type != ElementData::PLC) {
// Draw placeholder
painter->setBrush(QColor(255, 255, 200));
painter->drawRect(m_rect);
painter->drawText(m_rect, Qt::AlignCenter, QObject::tr("Table PLC"));
return;
}
const auto &plc_data = ed.plcMasterData();
if (plc_data.ios.isEmpty()) {
painter->setBrush(QColor(255, 255, 200));
painter->drawRect(m_rect);
painter->drawText(m_rect, Qt::AlignCenter, QObject::tr("Table PLC (vide)"));
return;
}
const int COL_TYPE = 0;
const int COL_ADDRESS = 1;
const int COL_FUNCTION = 2;
const int COL_COMMENT = 3;
const int COL_CROSSREF = 4;
const int COL_COUNT = 5;
QMap<int, QString> headers;
headers[COL_TYPE] = QObject::tr("Type");
headers[COL_ADDRESS] = QObject::tr("Adresse");
headers[COL_FUNCTION] = QObject::tr("Fonction");
headers[COL_COMMENT] = QObject::tr("Commentaire");
headers[COL_CROSSREF] = QObject::tr("Réf. croisée");
// Override with custom column names if set
if (!plc_data.columnNames.isEmpty()) {
QList<int> all_cols;
all_cols << COL_TYPE << COL_ADDRESS << COL_FUNCTION << COL_COMMENT << COL_CROSSREF;
for (int i = 0; i < qMin(plc_data.columnNames.size(), all_cols.size()); ++i) {
if (!plc_data.columnNames.at(i).isEmpty())
headers[all_cols.at(i)] = plc_data.columnNames.at(i);
}
}
QList<int> visible_cols;
if (!plc_data.columnOrder.isEmpty()) {
for (int logical : plc_data.columnOrder) {
if (logical >= 0 && logical < COL_COUNT
&& plc_data.colVisible.value(logical, true)
&& !visible_cols.contains(logical))
visible_cols.append(logical);
}
for (int i = 0; i < COL_COUNT; ++i) {
if (plc_data.colVisible.value(i, true) && !visible_cols.contains(i))
visible_cols.append(i);
}
} else {
for (int i = 0; i < COL_COUNT; ++i) {
if (plc_data.colVisible.value(i, true))
visible_cols.append(i);
}
}
if (visible_cols.isEmpty())
return;
QMap<int, qreal> col_widths;
for (int col : visible_cols) {
if (plc_data.colWidths.contains(col) && plc_data.colWidths[col] > 0)
col_widths[col] = plc_data.colWidths[col];
else {
switch (col) {
case COL_TYPE: col_widths[col] = 35; break;
case COL_ADDRESS: col_widths[col] = 25; break;
case COL_FUNCTION: col_widths[col] = 50; break;
case COL_COMMENT: col_widths[col] = 40; break;
case COL_CROSSREF: col_widths[col] = 30; break;
default: col_widths[col] = 30; break;
}
}
}
qreal row_h = plc_data.rowHeight > 0 ? plc_data.rowHeight : 8.0;
qreal header_h = plc_data.showHeaders ? (row_h + 2.0) : 0;
int total_ios = plc_data.ios.size();
// Collect active break positions (sorted)
QList<int> breaks;
for (int bp : plc_data.breakPositions) {
if (bp > 0 && bp < total_ios && !breaks.contains(bp))
breaks.append(bp);
}
std::sort(breaks.begin(), breaks.end());
// Build block boundaries: start, break1, break2, ..., end
QList<int> block_starts;
block_starts.append(0);
for (int bp : breaks)
block_starts.append(bp);
int block_count = block_starts.size();
// Draw background
painter->save();
painter->setPen(Qt::NoPen);
painter->setBrush(Qt::white);
painter->drawRect(m_rect);
painter->restore();
// Draw outer border
QPen border_pen(Qt::black, 0.5);
painter->setPen(border_pen);
painter->setBrush(Qt::NoBrush);
painter->drawRect(m_rect);
// Draw each block
for (int block = 0; block < block_count; ++block) {
qreal block_w = 0;
for (int col : visible_cols)
block_w += col_widths[col];
qreal block_x = m_rect.x() + block * (block_w + 3);
qreal cx = block_x;
// Draw column headers
QFont header_font = plc_data.headerFont.family().isEmpty()
? painter->font() : plc_data.headerFont;
header_font.setBold(true);
painter->setFont(header_font);
for (int col : visible_cols) {
QRectF header_rect(cx, m_rect.y(), col_widths[col], header_h);
painter->fillRect(header_rect, QColor(220, 220, 220));
painter->setPen(border_pen);
painter->drawRect(header_rect);
QString header_text = headers.value(col, QString());
painter->drawText(header_rect, Qt::AlignCenter, header_text);
cx += col_widths[col];
}
// Draw IO rows
QFont cell_font = plc_data.cellFont.family().isEmpty()
? painter->font() : plc_data.cellFont;
painter->setFont(cell_font);
int start_idx = block_starts.at(block);
int end_idx = (block + 1 < block_starts.size())
? block_starts.at(block + 1) : total_ios;
for (int row = 0; row < (end_idx - start_idx); ++row) {
int io_idx = start_idx + row;
const ElementData::PlcIO &io = plc_data.ios.at(io_idx);
qreal ry = m_rect.y() + header_h + row * row_h;
cx = block_x;
for (int col : visible_cols) {
QRectF cell_rect(cx, ry, col_widths[col], row_h);
painter->setPen(border_pen);
painter->drawRect(cell_rect);
QString cell_text;
switch (col) {
case COL_TYPE: cell_text = ElementData::translatedPlcIOType(io.type); break;
case COL_ADDRESS: cell_text = io.address; break;
case COL_FUNCTION: cell_text = io.functionText; break;
case COL_COMMENT: cell_text = io.comment; break;
case COL_CROSSREF: cell_text = io.crossRef; break;
}
QRectF text_rect = cell_rect.adjusted(1, 0, -1, 0);
painter->drawText(text_rect, Qt::AlignLeft | Qt::AlignVCenter, cell_text);
cx += col_widths[col];
}
}
}
if (m_hovered)
drawShadowShape(painter);
if (isSelected())
drawCross(m_rect.center(), painter);
}
/**
@brief PartPlcTable::toXml
Export this PLC table part in xml
@param xml_document : Xml document to use for create the xml element.
@return an xml element that describe this part
*/
const QDomElement PartPlcTable::toXml(QDomDocument &xml_document) const
{
QDomElement xml_element = xml_document.createElement("plc_table");
qreal x = qRound(m_rect.x() * 100.0) / 100.0;
qreal y = qRound(m_rect.y() * 100.0) / 100.0;
xml_element.setAttribute("x", QString::number(x));
xml_element.setAttribute("y", QString::number(y));
stylesToXml(xml_element);
return xml_element;
}
/**
@brief PartPlcTable::fromXml
Import the properties of this PLC table part from a xml element.
@param qde : Xml document to use.
*/
void PartPlcTable::fromXml(const QDomElement &qde)
{
stylesFromXml(qde);
qreal x = qde.attribute("x", "0").toDouble();
qreal y = qde.attribute("y", "0").toDouble();
setPos(mapFromScene(x, y));
// Auto-size from PLC data
QSizeF table_size = calculateTableSize();
prepareGeometryChange();
m_rect = QRectF(QPointF(0, 0), table_size);
update();
}
/**
@brief PartPlcTable::rect
@return : Returns the item's rectangle.
*/
QRectF PartPlcTable::rect() const
{
return m_rect;
}
/**
@brief PartPlcTable::setRect
Sets the item's rectangle to be the given rectangle.
@param rect
*/
void PartPlcTable::setRect(const QRectF &rect)
{
if (rect == m_rect) return;
prepareGeometryChange();
m_rect = rect;
adjustHandlerPos();
update();
}
/**
@brief PartPlcTable::sceneGeometricRect
@return the minimum, margin-less rectangle this part can fit into, in scene
coordinates.
*/
QRectF PartPlcTable::sceneGeometricRect() const
{
return(mapToScene(m_rect).boundingRect());
}
/**
@brief PartPlcTable::shape
@return the shape of this item
*/
QPainterPath PartPlcTable::shape() const
{
QPainterPath fill;
fill.addRect(m_rect);
QPainterPath stroke;
stroke.addRect(m_rect);
QPainterPathStroker pps;
pps.setWidth(m_hovered? penWeight()+SHADOWS_HEIGHT : penWeight());
stroke = pps.createStroke(stroke);
return fill.united(stroke);
}
QPainterPath PartPlcTable::shadowShape() const
{
QPainterPath shape;
shape.addRect(m_rect);
QPainterPathStroker pps;
pps.setWidth(penWeight());
return (pps.createStroke(shape));
}
/**
@brief PartPlcTable::boundingRect
@return Bounding rectangle this part can fit into
*/
QRectF PartPlcTable::boundingRect() const
{
qreal adjust = (SHADOWS_HEIGHT + penWeight()) / 2;
if (penWeight() == 0) adjust += 0.5;
QRectF r = m_rect.normalized();
r.adjust(-adjust, -adjust, adjust, adjust);
return(r);
}
/**
@brief PartPlcTable::isUseless
@return true if this part is irrelevant and does not deserve to be saved.
A PLC table part is always relevant.
*/
bool PartPlcTable::isUseless() const
{
return false;
}
/**
@brief PartPlcTable::startUserTransformation
@param initial_selection_rect
*/
void PartPlcTable::startUserTransformation(const QRectF &initial_selection_rect)
{
Q_UNUSED(initial_selection_rect)
saved_points_.clear();
saved_points_ << mapToScene(m_rect.topLeft()) << mapToScene(m_rect.bottomRight());
}
/**
@brief PartPlcTable::handleUserTransformation
@param initial_selection_rect
@param new_selection_rect
*/
void PartPlcTable::handleUserTransformation(const QRectF &initial_selection_rect, const QRectF &new_selection_rect)
{
QList<QPointF> mapped_points = mapPoints(initial_selection_rect, new_selection_rect, saved_points_);
setRect(QRectF(mapFromScene(mapped_points.at(0)), mapFromScene(mapped_points.at(1))));
}
/**
@brief PartPlcTable::mouseReleaseEvent
*/
void PartPlcTable::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
CustomElementGraphicPart::mouseReleaseEvent(event);
}
/**
@brief PartPlcTable::itemChange
@param change
@param value
@return
*/
QVariant PartPlcTable::itemChange(GraphicsItemChange change, const QVariant &value)
{
if (change == ItemPositionHasChanged)
{
adjustHandlerPos();
}
else if (change == ItemSceneChange)
{
setSelected(false);
}
else if (change == ItemSceneHasChanged)
{
if (ElementScene *es = elementScene()) {
connect(es, &ElementScene::elementInfoChanged,
this, [this]() {
QSizeF table_size = calculateTableSize();
if (m_rect.size() != table_size) {
QPointF top_left = m_rect.topLeft();
setRect(QRectF(top_left, table_size));
}
update();
});
}
}
return QGraphicsItem::itemChange(change, value);
}
/**
@brief PartPlcTable::sceneEventFilter
@param watched
@param event
@return
*/
bool PartPlcTable::sceneEventFilter(QGraphicsItem *watched, QEvent *event)
{
if(watched->type() == QetGraphicsHandlerItem::Type)
{
QetGraphicsHandlerItem *qghi = qgraphicsitem_cast<QetGraphicsHandlerItem *>(watched);
if(m_handler_vector.contains(qghi))
{
m_vector_index = m_handler_vector.indexOf(qghi);
if (m_vector_index != -1)
{
if(event->type() == QEvent::GraphicsSceneMousePress)
{
handlerMousePressEvent(qghi, static_cast<QGraphicsSceneMouseEvent *>(event));
return true;
}
else if(event->type() == QEvent::GraphicsSceneMouseMove)
{
handlerMouseMoveEvent(qghi, static_cast<QGraphicsSceneMouseEvent *>(event));
return true;
}
else if (event->type() == QEvent::GraphicsSceneMouseRelease)
{
handlerMouseReleaseEvent(qghi, static_cast<QGraphicsSceneMouseEvent *>(event));
return true;
}
}
}
}
return false;
}
/**
@brief PartPlcTable::switchResizeMode
*/
void PartPlcTable::switchResizeMode()
{
if (m_resize_mode == 1)
{
m_resize_mode = 2;
for (QetGraphicsHandlerItem *qghi : m_handler_vector)
qghi->setColor(Qt::darkGreen);
}
else
{
m_resize_mode = 1;
qDeleteAll(m_handler_vector);
m_handler_vector.clear();
addHandler();
for (QetGraphicsHandlerItem *qghi : m_handler_vector) {
qghi->setColor(Qt::blue);
}
}
}
/**
@brief PartPlcTable::adjustHandlerPos
*/
void PartPlcTable::adjustHandlerPos()
{
if (m_handler_vector.isEmpty())
return;
QVector<QPointF> points_vector = QetGraphicsHandlerUtility::pointsForRect(m_rect);
if (m_handler_vector.size() == points_vector.size())
{
points_vector = mapToScene(points_vector);
for (int i = 0 ; i < points_vector.size() ; ++i)
m_handler_vector.at(i)->setPos(points_vector.at(i));
}
else
{
qDeleteAll(m_handler_vector);
m_handler_vector.clear();
addHandler();
}
}
void PartPlcTable::handlerMousePressEvent(QetGraphicsHandlerItem *qghi, QGraphicsSceneMouseEvent *event)
{
Q_UNUSED(qghi)
Q_UNUSED(event)
m_old_rect = m_rect;
}
void PartPlcTable::handlerMouseMoveEvent(QetGraphicsHandlerItem *qghi, QGraphicsSceneMouseEvent *event)
{
Q_UNUSED(qghi)
QPointF new_pos = event->scenePos();
if (event->modifiers() != Qt::ControlModifier)
new_pos = elementScene()->snapToGrid(event->scenePos());
new_pos = mapFromScene(new_pos);
setRect(QetGraphicsHandlerUtility::rectForPosAtIndex(m_rect, new_pos, m_vector_index));
adjustHandlerPos();
}
void PartPlcTable::handlerMouseReleaseEvent(QetGraphicsHandlerItem *qghi, QGraphicsSceneMouseEvent *event)
{
Q_UNUSED(qghi)
Q_UNUSED(event)
QUndoCommand *undo = new QUndoCommand("Modifier une table PLC");
if (m_old_rect != m_rect) {
QPropertyUndoCommand *u = new QPropertyUndoCommand(this, "rect", QVariant(m_old_rect.normalized()), QVariant(m_rect.normalized()), undo);
u->setAnimated(true, false);
}
elementScene()->undoStack().push(undo);
m_vector_index = -1;
}
/**
@brief PartPlcTable::addHandler
Do not add resize handlers - table size is data-driven.
Move is handled by the base class QGraphicsItem drag behavior.
*/
void PartPlcTable::addHandler()
{
// No resize handlers - size comes from PLC data
}
/**
@brief PartPlcTable::removeHandler
Remove the handlers of this item
*/
void PartPlcTable::removeHandler()
{
if (!m_handler_vector.isEmpty())
{
qDeleteAll(m_handler_vector);
m_handler_vector.clear();
}
}
@@ -0,0 +1,89 @@
/*
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 PARTPLCTABLE_H
#define PARTPLCTABLE_H
#include "customelementgraphicpart.h"
#include "../../QetGraphicsItemModeler/qetgraphicshandleritem.h"
#include <QVector>
/**
@brief The PartPlcTable class
This class represents a PLC I/O table preview in the element editor.
It shows the user where the PLC table will appear at runtime, so they
can position other element parts (rectangles, terminals, etc.) around it.
The actual PLC data is read from ElementScene::elementData().
*/
class PartPlcTable : public CustomElementGraphicPart
{
Q_OBJECT
Q_PROPERTY(QRectF rect READ rect WRITE setRect)
public:
PartPlcTable(QETElementEditor *editor, QGraphicsItem *parent = nullptr);
~PartPlcTable() override;
enum { Type = UserType + 1120 };
int type () const override { return Type; }
void paint (QPainter *, const QStyleOptionGraphicsItem *, QWidget * = nullptr) override;
QString name () const override { return QObject::tr("table PLC", "element part name"); }
QString xmlName () const override { return QString("plc_table"); }
const QDomElement toXml (QDomDocument &) const override;
void fromXml (const QDomElement &) override;
QRectF rect() const;
void setRect(const QRectF &rect);
QRectF sceneGeometricRect() const override;
QPainterPath shape () const override;
QPainterPath shadowShape() const override;
QRectF boundingRect() const override;
bool isUseless() const override;
void startUserTransformation(const QRectF &) override;
void handleUserTransformation(const QRectF &, const QRectF &) override;
void addHandler() override;
void removeHandler() override;
protected:
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;
QVariant itemChange(GraphicsItemChange change, const QVariant &value) override;
bool sceneEventFilter(QGraphicsItem *watched, QEvent *event) override;
private:
void switchResizeMode();
void adjustHandlerPos();
void handlerMousePressEvent (QetGraphicsHandlerItem *qghi, QGraphicsSceneMouseEvent *event);
void handlerMouseMoveEvent (QetGraphicsHandlerItem *qghi, QGraphicsSceneMouseEvent *event);
void handlerMouseReleaseEvent (QetGraphicsHandlerItem *qghi, QGraphicsSceneMouseEvent *event);
QSizeF calculateTableSize() const;
private:
QRectF m_rect,
m_old_rect;
QList<QPointF> saved_points_;
int m_resize_mode = 1,
m_vector_index = -1;
QVector<QetGraphicsHandlerItem *> m_handler_vector;
};
#endif // PARTPLCTABLE_H
+245 -1
View File
@@ -17,6 +17,8 @@
*/
#include "partterminal.h"
#include "../elementscene.h"
#include "../../QPropertyUndoCommand/qpropertyundocommand.h"
#include "../../qetgraphicsitem/terminal.h"
/**
@@ -100,6 +102,60 @@ void PartTerminal::paint(
if (m_hovered)
drawShadowShape(painter);
if (d->m_show_name && !d->m_name.isEmpty()) {
painter->save();
painter->setFont(d->m_label_font);
painter->setPen(d->m_label_color);
painter->setRenderHint(QPainter::Antialiasing, true);
painter->setRenderHint(QPainter::TextAntialiasing, true);
QPointF label_pos = d->m_label_pos;
QRectF text_rect;
QFontMetrics fm(d->m_label_font);
QSizeF text_size = fm.size(Qt::TextSingleLine, d->m_name);
auto compute_rect = [&]() {
qreal dx = 0, dy = 0;
if (d->m_label_halignment & Qt::AlignLeft) dx = 0;
else if (d->m_label_halignment & Qt::AlignHCenter) dx = -text_size.width() / 2.0;
else if (d->m_label_halignment & Qt::AlignRight) dx = -text_size.width();
if (d->m_label_valignment & Qt::AlignTop) dy = 0;
else if (d->m_label_valignment & Qt::AlignVCenter) dy = -text_size.height() / 2.0;
else if (d->m_label_valignment & Qt::AlignBottom) dy = -text_size.height();
return QRectF(label_pos + QPointF(dx, dy), text_size);
};
if (d->m_label_rotation != 0.0) {
painter->translate(label_pos);
painter->rotate(d->m_label_rotation);
text_rect = QRectF(-text_size.width()/2.0, -text_size.height()/2.0,
text_size.width(), text_size.height());
if (d->m_label_frame) {
painter->drawRect(text_rect.adjusted(-1, -1, 1, 1));
}
painter->drawText(text_rect, static_cast<int>(d->m_label_halignment | d->m_label_valignment), d->m_name);
} else {
text_rect = compute_rect();
if (d->m_label_frame) {
painter->drawRect(text_rect.adjusted(-1, -1, 1, 1));
}
painter->drawText(text_rect, static_cast<int>(Qt::AlignLeft | Qt::AlignTop), d->m_name);
}
if (isSelected() || m_hovered) {
QPen outline_pen(Qt::darkBlue, 0, Qt::DashLine);
painter->setPen(outline_pen);
painter->setBrush(Qt::NoBrush);
if (d->m_label_rotation != 0.0) {
painter->drawRect(text_rect.adjusted(-1, -1, 1, 1));
} else {
painter->drawRect(text_rect.adjusted(-1, -1, 1, 1));
}
}
painter->restore();
}
}
/**
@@ -114,7 +170,27 @@ QPainterPath PartTerminal::shape() const
QPainterPathStroker pps;
pps.setWidth(1);
return (pps.createStroke(shape));
QPainterPath path = pps.createStroke(shape);
if (d->m_show_name && !d->m_name.isEmpty()) {
path.addRect(labelRect());
}
return path;
}
/**
@brief PartTerminal::shadowShape
@return the hover outline shape (terminal line only, no label rect)
*/
QPainterPath PartTerminal::shadowShape() const
{
QPainterPath shape;
shape.lineTo(d -> m_second_point);
QPainterPathStroker pps;
pps.setWidth(1);
return pps.createStroke(shape);
}
/**
@@ -128,9 +204,40 @@ QRectF PartTerminal::boundingRect() const
qreal adjust = (SHADOWS_HEIGHT + 1) / 2;
br.adjust(-adjust, -adjust, adjust, adjust);
if (d->m_show_name && !d->m_name.isEmpty()) {
br = br.united(labelRect());
}
return(br);
}
/**
@brief PartTerminal::labelRect
@return the rectangle of the label text (in item coordinates),
or an empty rect if the label is not shown
*/
QRectF PartTerminal::labelRect() const
{
if (!d->m_show_name || d->m_name.isEmpty())
return QRectF();
QFontMetrics fm(d->m_label_font);
QSizeF text_size = fm.size(Qt::TextSingleLine, d->m_name);
QPointF label_pos = d->m_label_pos;
qreal dx = 0, dy = 0;
if (d->m_label_halignment & Qt::AlignLeft) dx = 0;
else if (d->m_label_halignment & Qt::AlignHCenter) dx = -text_size.width() / 2.0;
else if (d->m_label_halignment & Qt::AlignRight) dx = -text_size.width();
if (d->m_label_valignment & Qt::AlignTop) dy = 0;
else if (d->m_label_valignment & Qt::AlignVCenter) dy = -text_size.height() / 2.0;
else if (d->m_label_valignment & Qt::AlignBottom) dy = -text_size.height();
return QRectF(label_pos + QPointF(dx, dy), text_size).adjusted(-3, -3, 3, 3);
}
/**
Definit l'orientation de la borne
@@ -282,6 +389,93 @@ void PartTerminal::setNewUuid()
d -> m_uuid = QUuid::createUuid();
}
void PartTerminal::setShowName(bool show)
{
if (d->m_show_name == show) return;
prepareGeometryChange();
d->m_show_name = show;
update();
emit showNameChanged();
}
void PartTerminal::setLabelPos(QPointF pos)
{
if (d->m_label_pos == pos) return;
prepareGeometryChange();
d->m_label_pos = pos;
update();
emit labelPosChanged();
}
void PartTerminal::setLabelFont(QFont font)
{
if (d->m_label_font == font) return;
prepareGeometryChange();
d->m_label_font = font;
update();
emit labelFontChanged();
}
void PartTerminal::setLabelRotation(qreal rotation)
{
if (qFuzzyCompare(d->m_label_rotation, rotation)) return;
prepareGeometryChange();
d->m_label_rotation = rotation;
update();
emit labelRotationChanged();
}
void PartTerminal::setLabelHAlignment(Qt::Alignment align)
{
if (d->m_label_halignment == align) return;
prepareGeometryChange();
d->m_label_halignment = align;
update();
emit labelHAlignmentChanged();
}
void PartTerminal::setLabelVAlignment(Qt::Alignment align)
{
if (d->m_label_valignment == align) return;
prepareGeometryChange();
d->m_label_valignment = align;
update();
emit labelVAlignmentChanged();
}
void PartTerminal::setLabelFrame(bool frame)
{
if (d->m_label_frame == frame) return;
prepareGeometryChange();
d->m_label_frame = frame;
update();
emit labelFrameChanged();
}
void PartTerminal::setLabelColor(QColor color)
{
if (d->m_label_color == color) return;
d->m_label_color = color;
update();
emit labelColorChanged();
}
void PartTerminal::setUseMasterLabel(bool use)
{
if (d->m_use_master_label == use) return;
d->m_use_master_label = use;
update();
emit useMasterLabelChanged();
}
void PartTerminal::setMasterLabelIndex(int index)
{
if (d->m_master_label_index == index) return;
d->m_master_label_index = index;
update();
emit masterLabelIndexChanged();
}
/**
Updates the position of the second point according to the position
and orientation of the terminal.
@@ -338,3 +532,53 @@ void PartTerminal::handleUserTransformation(const QRectF &initial_selection_rect
initial_selection_rect, new_selection_rect, QList<QPointF>() << saved_position_).first();
setPos(mapped_point);
}
void PartTerminal::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
if (event->button() == Qt::LeftButton && d->m_show_name && !d->m_name.isEmpty()) {
QPointF local_click = event->pos();
// Only start label drag when click is on label AND NOT on terminal line
if (!shadowShape().contains(local_click) && labelRect().contains(local_click)) {
m_dragging_label = true;
m_original_label_pos = d->m_label_pos;
}
}
CustomElementGraphicPart::mousePressEvent(event);
}
void PartTerminal::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
if (m_dragging_label) {
QPointF delta = event->pos() - event->buttonDownPos(Qt::LeftButton);
QPointF new_pos = m_original_label_pos + delta;
if (!(event->modifiers() & Qt::ControlModifier)) {
ElementScene *scene = elementScene();
if (scene) {
QPointF scene_pos = mapToScene(new_pos);
new_pos = mapFromScene(scene->snapToGrid(scene_pos));
}
}
setLabelPos(new_pos);
update();
event->accept();
return;
}
CustomElementGraphicPart::mouseMoveEvent(event);
}
void PartTerminal::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
if (m_dragging_label) {
m_dragging_label = false;
if (m_original_label_pos != d->m_label_pos) {
auto undo = new QPropertyUndoCommand(this, "label_pos",
QVariant(m_original_label_pos), QVariant(d->m_label_pos));
undo->setText(tr("Déplacer le label d'une borne"));
undo->enableAnimation();
elementScene()->undoStack().push(undo);
}
event->accept();
return;
}
CustomElementGraphicPart::mouseReleaseEvent(event);
}
+60 -1
View File
@@ -34,6 +34,16 @@ class PartTerminal : public CustomElementGraphicPart
Q_PROPERTY(Qet::Orientation orientation READ orientation WRITE setOrientation)
Q_PROPERTY(QString terminal_name READ terminalName WRITE setTerminalName)
Q_PROPERTY(TerminalData::Type terminal_type READ terminalType WRITE setTerminalType)
Q_PROPERTY(bool show_name READ showName WRITE setShowName)
Q_PROPERTY(QPointF label_pos READ labelPos WRITE setLabelPos)
Q_PROPERTY(QFont label_font READ labelFont WRITE setLabelFont)
Q_PROPERTY(qreal label_rotation READ labelRotation WRITE setLabelRotation)
Q_PROPERTY(Qt::Alignment label_halignment READ labelHAlignment WRITE setLabelHAlignment)
Q_PROPERTY(Qt::Alignment label_valignment READ labelVAlignment WRITE setLabelVAlignment)
Q_PROPERTY(bool label_frame READ labelFrame WRITE setLabelFrame)
Q_PROPERTY(QColor label_color READ labelColor WRITE setLabelColor)
Q_PROPERTY(bool use_master_label READ useMasterLabel WRITE setUseMasterLabel)
Q_PROPERTY(int master_label_index READ masterLabelIndex WRITE setMasterLabelIndex)
public:
// constructors, destructor
@@ -46,6 +56,16 @@ class PartTerminal : public CustomElementGraphicPart
void orientationChanged();
void nameChanged();
void terminalTypeChanged();
void showNameChanged();
void labelPosChanged();
void labelFontChanged();
void labelRotationChanged();
void labelHAlignmentChanged();
void labelVAlignmentChanged();
void labelFrameChanged();
void labelColorChanged();
void useMasterLabelChanged();
void masterLabelIndexChanged();
// methods
public:
@@ -64,7 +84,7 @@ class PartTerminal : public CustomElementGraphicPart
QWidget *) override;
QPainterPath shape() const override;
QPainterPath shadowShape() const override {return shape();}
QPainterPath shadowShape() const override;
QRectF boundingRect() const override;
bool isUseless() const override;
QRectF sceneGeometricRect() const override;
@@ -90,13 +110,52 @@ class PartTerminal : public CustomElementGraphicPart
TerminalData::Type terminalType() const {return d->m_type;}
void setTerminalType(TerminalData::Type type);
bool showName() const { return d->m_show_name; }
void setShowName(bool show);
QPointF labelPos() const { return d->m_label_pos; }
void setLabelPos(QPointF pos);
QFont labelFont() const { return d->m_label_font; }
void setLabelFont(QFont font);
qreal labelRotation() const { return d->m_label_rotation; }
void setLabelRotation(qreal rotation);
Qt::Alignment labelHAlignment() const { return d->m_label_halignment; }
void setLabelHAlignment(Qt::Alignment align);
Qt::Alignment labelVAlignment() const { return d->m_label_valignment; }
void setLabelVAlignment(Qt::Alignment align);
bool labelFrame() const { return d->m_label_frame; }
void setLabelFrame(bool frame);
QColor labelColor() const { return d->m_label_color; }
void setLabelColor(QColor color);
bool useMasterLabel() const { return d->m_use_master_label; }
void setUseMasterLabel(bool use);
int masterLabelIndex() const { return d->m_master_label_index; }
void setMasterLabelIndex(int index);
void setNewUuid();
QRectF labelRect() const;
protected:
void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override;
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;
private:
void updateSecondPoint();
TerminalData* d; // pointer to the terminal data
private:
QPointF saved_position_;
bool m_dragging_label = false;
QPointF m_original_label_pos;
};
#endif
+149 -8
View File
@@ -18,10 +18,12 @@
#include "parttext.h"
#include "../../QPropertyUndoCommand/qpropertyundocommand.h"
#include <QApplication>
#include "../../qetapp.h"
#include "../elementprimitivedecorator.h"
#include "../elementscene.h"
#include "../ui/texteditor.h"
#include "../../utils/qetutils.h"
/**
Constructeur
@@ -124,12 +126,26 @@ void PartText::fromXml(const QDomElement &xml_element) {
}
else if (xml_element.hasAttribute("font")) {
QFont font_;
font_.fromString(xml_element.attribute("font"));
QETUtils::fontFromString(font_, xml_element.attribute("font"));
setFont(font_);
}
setDefaultTextColor(QColor(xml_element.attribute("color", "#000000")));
setPlainText(xml_element.attribute("text"));
// Optional alignment (absent = historical behaviour: top-left anchor,
// left-aligned lines), same attributes as the dynamic text fields.
Qt::Alignment alignment_ = Qt::AlignTop | Qt::AlignLeft;
QMetaEnum me = QMetaEnum::fromType<Qt::Alignment>();
if (xml_element.hasAttribute("Halignment"))
alignment_ = Qt::Alignment(
me.keyToValue(xml_element.attribute("Halignment").toStdString().data()));
if (xml_element.hasAttribute("Valignment"))
alignment_ = Qt::Alignment(
me.keyToValue(xml_element.attribute("Valignment").toStdString().data()))
| (alignment_ & Qt::AlignHorizontal_Mask);
setAlignment(alignment_);
setPos(xml_element.attribute("x").toDouble(),
xml_element.attribute("y").toDouble());
QGraphicsObject::setRotation(QET::correctAngle(xml_element.attribute("rotation", QString::number(0)).toDouble()));
@@ -150,10 +166,22 @@ const QDomElement PartText::toXml(QDomDocument &xml_document) const
xml_element.setAttribute("x", QString::number(x));
xml_element.setAttribute("y", QString::number(y));
xml_element.setAttribute("text", toPlainText());
xml_element.setAttribute("font", font().toString());
xml_element.setAttribute("font", QETUtils::fontToString(font()));
xml_element.setAttribute("rotation", QString::number(rot));
xml_element.setAttribute("color", defaultTextColor().name());
// Only written when different from the historical behaviour, so
// existing .elmt files round-trip byte-identical.
QMetaEnum me = QMetaEnum::fromType<Qt::Alignment>();
if (m_alignment & Qt::AlignRight)
xml_element.setAttribute("Halignment", me.valueToKey(Qt::AlignRight));
else if (m_alignment & Qt::AlignHCenter)
xml_element.setAttribute("Halignment", me.valueToKey(Qt::AlignHCenter));
if (m_alignment & Qt::AlignBottom)
xml_element.setAttribute("Valignment", me.valueToKey(Qt::AlignBottom));
else if (m_alignment & Qt::AlignVCenter)
xml_element.setAttribute("Valignment", me.valueToKey(Qt::AlignVCenter));
return(xml_element);
}
@@ -305,24 +333,126 @@ void PartText::setDefaultTextColor(const QColor &color) {
void PartText::setPlainText(const QString &text) {
if (text != this -> toPlainText()) {
prepareAlignment();
QGraphicsTextItem::setPlainText(text);
applyLineAlignment();
finishAlignment();
emit plainTextChanged(text);
}
}
/**
@brief PartText::setAlignment
Set how this text is anchored to its position: when the content later
changes, the given corner/center of the bounding rect keeps its place
(the historical behaviour, and the default, is top-left). The
horizontal part also aligns the lines of a multi-line text relative
to each other. Changing the alignment never moves the text itself.
@param alignment
*/
void PartText::setAlignment(const Qt::Alignment &alignment)
{
if (alignment == m_alignment)
return;
m_alignment = alignment;
applyLineAlignment();
emit alignmentChanged(m_alignment);
}
/**
@brief PartText::applyLineAlignment
Align the lines of a multi-line text relative to each other according
to the horizontal part of the alignment property. QGraphicsTextItem
only honors the document text option when a text width is set, hence
the idealWidth() dance; -1 restores the free (historical) layout.
*/
void PartText::applyLineAlignment()
{
QTextOption option = document()->defaultTextOption();
option.setAlignment(m_alignment & Qt::AlignHorizontal_Mask);
document()->setDefaultTextOption(option);
setTextWidth(-1);
if (m_alignment & (Qt::AlignHCenter | Qt::AlignRight))
setTextWidth(document()->idealWidth());
}
/**
@brief PartText::prepareAlignment
Call before a change of the bounding rect (see finishAlignment).
*/
void PartText::prepareAlignment()
{
m_alignment_rect = boundingRect();
}
/**
@brief PartText::finishAlignment
Call after a change of the bounding rect: moves the text so that the
point selected by the alignment property stays where it was (same
logic as DiagramTextItem::finishAlignment).
*/
void PartText::finishAlignment()
{
QTransform transform;
transform.rotate(rotation());
qreal x, xa, y, ya;
x = xa = 0;
y = ya = 0;
if (m_alignment & Qt::AlignRight)
{
x = m_alignment_rect.right();
xa = boundingRect().right();
}
else if (m_alignment & Qt::AlignHCenter)
{
x = m_alignment_rect.center().x();
xa = boundingRect().center().x();
}
if (m_alignment & Qt::AlignBottom)
{
y = m_alignment_rect.bottom();
ya = boundingRect().bottom();
}
else if (m_alignment & Qt::AlignVCenter)
{
y = m_alignment_rect.center().y();
ya = boundingRect().center().y();
}
QPointF p = transform.map(QPointF(x, y));
QPointF pa = transform.map(QPointF(xa, ya));
setPos(pos() - (pa - p));
}
void PartText::setFont(const QFont &font) {
if (font != this -> font()) {
prepareAlignment();
QGraphicsTextItem::setFont(font);
applyLineAlignment();
finishAlignment();
// Re-anchor: the item's position transform is -margin(), and margin()
// depends on the font ascent. Without re-running this on a font change,
// the transform keeps the previous font's ascent — so the text renders
// at a different spot after save/reopen (the position recomputes from
// the saved font on load). See #158.
adjustItemPosition();
emit fontChanged(font);
}
}
void PartText::mouseMoveEvent(QGraphicsSceneMouseEvent *event) {
if((event -> buttons() & Qt::LeftButton) && (flags() & QGraphicsItem::ItemIsMovable)) {
QPointF pos = event -> scenePos() + (m_origin_pos - event -> buttonDownScenePos(Qt::LeftButton));
event -> modifiers() == Qt::ControlModifier ? setPos(pos) : setPos(elementScene() -> snapToGrid(pos));
}
else {
if ((event->buttons() & Qt::LeftButton) && (flags() & QGraphicsItem::ItemIsMovable)) {
// Suppress spurious moves from the properties dock resizing the viewport.
const QPointF d = event->screenPos() - event->buttonDownScreenPos(Qt::LeftButton);
if (d.manhattanLength() < QApplication::startDragDistance())
return;
QPointF pos = event->scenePos() + (m_origin_pos - event->buttonDownScenePos(Qt::LeftButton));
event->modifiers() == Qt::ControlModifier ? setPos(pos) : setPos(elementScene()->snapToGrid(pos));
} else {
QGraphicsObject::mouseMoveEvent(event);
}
}
@@ -387,6 +517,11 @@ void PartText::startEdition()
{
// !previous_text.isNull() means the text is being edited
previous_text = toPlainText();
// Anchor the aligned point across the whole inline edition; free the
// text width so typing is not wrapped at the previous block width.
prepareAlignment();
setTextWidth(-1);
}
/**
@@ -395,7 +530,8 @@ void PartText::startEdition()
*/
void PartText::endEdition()
{
if (!previous_text.isNull()) {
const bool was_editing = !previous_text.isNull();
if (was_editing) {
// the text was being edited
QString new_text = toPlainText();
if (previous_text != new_text) {
@@ -412,4 +548,9 @@ void PartText::endEdition()
setTextCursor(qtc);
setEditable(false);
if (was_editing) {
applyLineAlignment();
finishAlignment();
}
}
+9
View File
@@ -34,11 +34,13 @@ class PartText : public QGraphicsTextItem, public CustomElementPart {
Q_PROPERTY(QColor color READ defaultTextColor WRITE setDefaultTextColor NOTIFY colorChanged)
Q_PROPERTY(QString text READ toPlainText WRITE setPlainText NOTIFY plainTextChanged)
Q_PROPERTY(QFont font READ font WRITE setFont NOTIFY fontChanged)
Q_PROPERTY(Qt::Alignment alignment READ alignment WRITE setAlignment NOTIFY alignmentChanged)
signals:
void fontChanged(const QFont &font);
void colorChanged(const QColor &color);
void plainTextChanged(const QString &text);
void alignmentChanged(Qt::Alignment alignment);
// constructors, destructor
public:
@@ -77,6 +79,8 @@ class PartText : public QGraphicsTextItem, public CustomElementPart {
void setDefaultTextColor(const QColor &color);
void setPlainText(const QString &text);
void setFont(const QFont &font);
void setAlignment(const Qt::Alignment &alignment);
Qt::Alignment alignment() const {return m_alignment;}
public slots:
void adjustItemPosition(int = 0);
@@ -97,11 +101,16 @@ class PartText : public QGraphicsTextItem, public CustomElementPart {
private:
QPointF margin() const;
void applyLineAlignment();
void prepareAlignment();
void finishAlignment();
QString previous_text;
qreal real_font_size_;
QPointF saved_point_;
qreal saved_font_size_;
QGraphicsItem *decorator_;
QPointF m_origin_pos;
Qt::Alignment m_alignment = (Qt::AlignTop | Qt::AlignLeft);
QRectF m_alignment_rect;
};
#endif
+1 -1
View File
@@ -386,7 +386,7 @@ StyleEditor::StyleEditor(QETElementEditor *editor, CustomElementGraphicPart *p,
outline_color->setSizeAdjustPolicy(QComboBox::AdjustToContents);
filling_color->setSizeAdjustPolicy(QComboBox::AdjustToContents);
auto grid_layout = new QGridLayout(this);
auto grid_layout = new QGridLayout();
grid_layout->addWidget(new QLabel(tr("Contour :")), 0,0, Qt::AlignRight);
grid_layout->addWidget(outline_color, 0, 1);
grid_layout->addWidget(new QLabel(tr("Remplissage :")), 1, 0, Qt::AlignRight);
+41 -3
View File
@@ -163,7 +163,7 @@ void DynamicTextFieldEditor::updateForm()
}
}
on_m_text_from_cb_activated(ui -> m_text_from_cb -> currentIndex()); //For enable the good widget
updateTextFromWidgetsEnabled(ui -> m_text_from_cb -> currentIndex()); //For enable the good widget
}
}
@@ -197,6 +197,10 @@ void DynamicTextFieldEditor::setUpConnections()
m_connection_list << connect(m_text_field.data(), &PartDynamicTextField::textWidthChanged, this, [=](){this -> updateForm();});
m_connection_list << connect(m_text_field.data(), &PartDynamicTextField::compositeTextChanged,this, [=](){this -> updateForm();});
m_connection_list << connect(m_text_field.data(), &PartDynamicTextField::keepVisualRotationChanged, this, [=](){this -> updateForm();});
// Refresh info combo when element data changes (e.g. type switched to PLC-Slave)
m_connection_list << connect(elementEditor()->elementScene(), &ElementScene::elementInfoChanged,
this, &DynamicTextFieldEditor::fillInfoComboBox);
}
void DynamicTextFieldEditor::disconnectConnections()
@@ -218,13 +222,34 @@ void DynamicTextFieldEditor::fillInfoComboBox()
ui -> m_elmt_info_cb -> clear();
QStringList strl;
auto type = elementEditor()->elementScene()->elementData().m_type;
auto ed = elementEditor()->elementScene()->elementData();
auto type = ed.m_type;
if((type & ElementData::AllReport) || (type == ElementData::ConductorDefinition)) {
strl = QETInformation::folioReportInfoKeys();
}
else {
strl = QETInformation::elementInfoKeys();
bool is_plc_slave = (type == ElementData::Slave
&& ed.m_slave_type == ElementData::PLCSlave);
if (is_plc_slave) {
QStringList plc_keys = {
QETInformation::ELMT_PLC_TYPE,
QETInformation::ELMT_PLC_ADDRESS,
QETInformation::ELMT_PLC_FUNCTION,
QETInformation::ELMT_PLC_COMMENT,
QETInformation::ELMT_PLC_CROSSREF
};
strl = plc_keys + strl;
} else {
strl.removeAll(QETInformation::ELMT_PLC_TYPE);
strl.removeAll(QETInformation::ELMT_PLC_ADDRESS);
strl.removeAll(QETInformation::ELMT_PLC_FUNCTION);
strl.removeAll(QETInformation::ELMT_PLC_COMMENT);
strl.removeAll(QETInformation::ELMT_PLC_CROSSREF);
}
}
for (int i=0; i<strl.size();++i) {
@@ -327,7 +352,16 @@ void DynamicTextFieldEditor::on_m_elmt_info_cb_activated(const QString &arg1) {
}
}
void DynamicTextFieldEditor::on_m_text_from_cb_activated(int index) {
/**
@brief DynamicTextFieldEditor::updateTextFromWidgetsEnabled
Enable the widget matching @p index (the "text from" combo box's current
index) and disable the other two. Purely cosmetic: called both from the
real user-activated slot below and from updateForm() when the form is
(re)filled for a part/selection, so it must never touch m_parts's data —
see on_m_text_from_cb_activated() for the part-mutating counterpart.
*/
void DynamicTextFieldEditor::updateTextFromWidgetsEnabled(int index)
{
ui -> m_user_text_le -> setDisabled(true);
ui -> m_elmt_info_cb -> setDisabled(true);
ui -> m_composite_text_pb -> setDisabled(true);
@@ -341,6 +375,10 @@ void DynamicTextFieldEditor::on_m_text_from_cb_activated(int index) {
else {
ui->m_composite_text_pb->setEnabled(true);
}
}
void DynamicTextFieldEditor::on_m_text_from_cb_activated(int index) {
updateTextFromWidgetsEnabled(index);
DynamicElementTextItem::TextFrom tf;
if(index == 0) {
@@ -52,6 +52,7 @@ class DynamicTextFieldEditor : public ElementItemEditor {
void fillInfoComboBox();
void setUpConnections();
void disconnectConnections();
void updateTextFromWidgetsEnabled(int index);
private slots:
void on_m_x_sb_editingFinished();
File diff suppressed because it is too large Load Diff
@@ -24,6 +24,13 @@
#include <QAbstractButton>
#include <QDialog>
class QTableWidget;
class QSpinBox;
class QCheckBox;
class QGroupBox;
class QPushButton;
class QLineEdit;
namespace Ui {
class ElementPropertiesEditorWidget;
}
@@ -49,16 +56,45 @@ class ElementPropertiesEditorWidget : public QDialog
void setUpInterface();
void updateTree();
void populateTree();
void populateSlaveGroupsTable();
void readSlaveGroupsFromTable();
void createPlcConfigWidgets();
void populatePlcTable();
void readPlcTable();
//SLOTS
private slots:
void on_m_buttonBox_accepted();
void on_m_base_type_cb_currentIndexChanged(int index);
void on_m_slave_groups_checkbox_toggled(bool checked);
void on_max_slaves_checkbox_toggled(bool checked);
void plcAddRow();
void plcRemoveRow();
void plcPasteFromClipboard();
void plcTerminalCountChanged(int row, int count);
void plcSelectHeaderFont();
void plcSelectCellFont();
//ATTRIBUTES
private:
Ui::ElementPropertiesEditorWidget *ui;
ElementData m_data;
// PLC configuration widgets (created programmatically)
QGroupBox *m_plc_gb = nullptr;
QTableWidget *m_plc_table = nullptr;
QTableWidget *m_plc_terminal_table = nullptr;
QCheckBox *m_plc_break_checkboxes[4] = {nullptr, nullptr, nullptr, nullptr};
QSpinBox *m_plc_break_spinboxes[4] = {nullptr, nullptr, nullptr, nullptr};
QSpinBox *m_plc_row_height_spinbox = nullptr;
QPushButton *m_plc_header_font_btn = nullptr;
QPushButton *m_plc_cell_font_btn = nullptr;
QCheckBox *m_plc_show_headers_cb = nullptr;
QFont m_plc_header_font;
QFont m_plc_cell_font;
QList<QCheckBox *> m_plc_col_visibility_checkboxes;
QList<QSpinBox *> m_plc_col_width_spinboxes;
QList<QLineEdit *> m_plc_col_name_edits;
};
#endif // ELEMENTPROPERTIESEDITORWIDGET_H
@@ -93,17 +93,17 @@
<property name="title">
<string>Élément maître</string>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Type concret</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="m_master_type_cb"/>
</item>
<layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Type concret</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="m_master_type_cb"/>
</item>
<item row="1" column="0">
<widget class="QCheckBox" name="max_slaves_checkbox">
<property name="text">
@@ -121,6 +121,55 @@
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QCheckBox" name="m_slave_groups_checkbox">
<property name="text">
<string>Définir les éléments esclave</string>
</property>
</widget>
</item>
<item row="3" column="0" colspan="2">
<widget class="QTableWidget" name="m_slave_groups_table">
<property name="enabled">
<bool>false</bool>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>150</height>
</size>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<property name="columnCount">
<number>4</number>
</property>
<column>
<property name="text">
<string>Type</string>
</property>
</column>
<column>
<property name="text">
<string>Contact</string>
</property>
</column>
<column>
<property name="text">
<string>Nb. contacts</string>
</property>
</column>
<column>
<property name="text">
<string>Nb. bornes</string>
</property>
</column>
</widget>
</item>
</layout>
</widget>
</item>
+51 -47
View File
@@ -480,15 +480,11 @@ void QETElementEditor::fillPartsList()
}
}
QListWidgetItem *qlwi = new QListWidgetItem(part_desc);
QVariant v;
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) // ### Qt 6: remove
v.setValue<QGraphicsItem *>(qgi);
#else
#if TODO_LIST
#pragma message("@TODO remove code for QT 6 or later")
#endif
qDebug()<<"Help code for QT 6 or later";
#endif
// Qt declares the QGraphicsItem* metatype itself, so this
// works on Qt 5 and Qt 6 alike. Without the stored pointer
// the parts list loses its item association and selecting
// a part no longer selects it on the canvas.
QVariant v = QVariant::fromValue(qgi);
qlwi -> setData(42, v);
m_parts_list -> addItem(qlwi);
qlwi -> setSelected(qgi -> isSelected());
@@ -736,11 +732,13 @@ bool QETElementEditor::checkElement()
QList<QETWarning> warnings;
QList<QETWarning> errors;
// Warning #1: Element haven't got terminal
// Warning #1: Element does not have (enough) terminals
// (except for report and conductor definition, because they must have one terminal and this checking is done below)
// (another exception: "thumbnails" aka "front-views" may/should not have terminals)
if (!m_elmt_scene -> containsTerminals() &&
!(m_elmt_scene->elementData().m_type & ElementData::AllReport) &&
m_elmt_scene->elementData().m_type != ElementData::ConductorDefinition) {
m_elmt_scene->elementData().m_type != ElementData::ConductorDefinition &&
m_elmt_scene->elementData().m_type != ElementData::Thumbnail) {
warnings << qMakePair(
tr("Absence de borne", "warning title"),
tr(
@@ -749,50 +747,50 @@ bool QETElementEditor::checkElement()
"warning description"
)
);
}
}
// Check folio report element
if (m_elmt_scene->elementData().m_type & ElementData::AllReport)
{
int terminal =0;
// Check folio report element
if (m_elmt_scene->elementData().m_type & ElementData::AllReport)
{
int terminal =0;
for(auto qgi : m_elmt_scene -> items()) {
if (qgraphicsitem_cast<PartTerminal *>(qgi)) {
terminal ++;
}
}
//Error folio report must have only one terminal
if (terminal != 1) {
errors << qMakePair (tr("Absence de borne"),
tr("<br><b>Erreur</b> :"
"<br>Les reports de folio doivent posséder une seul borne."
"<br><b>Solution</b> :"
"<br>Verifier que l'élément ne possède qu'une seul borne"));
for(auto qgi : m_elmt_scene -> items()) {
if (qgraphicsitem_cast<PartTerminal *>(qgi)) {
terminal ++;
}
}
// Check conductor definition element
if (m_elmt_scene->elementData().m_type == ElementData::ConductorDefinition)
{
int terminal =0;
//Error folio report must have only one terminal
if (terminal != 1) {
errors << qMakePair (tr("Absence de borne"),
tr("<br><b>Erreur</b> :"
"<br>Les reports de folio doivent posséder une seul borne."
"<br><b>Solution</b> :"
"<br>Verifier que l'élément ne possède qu'une seul borne"));
}
}
for(auto qgi : m_elmt_scene -> items()) {
if (qgraphicsitem_cast<PartTerminal *>(qgi)) {
terminal ++;
}
}
// Check conductor definition element
if (m_elmt_scene->elementData().m_type == ElementData::ConductorDefinition)
{
int terminal =0;
// Error: Conductor definition must have exactly one terminal
if (terminal != 1) {
errors << qMakePair (tr("Nombre de bornes incorrect"),
tr("<br><b>Erreur</b> :"
"<br>Les définitions de conducteur ne peuvent posséder qu'une seule borne."
"<br><b>Solution</b> :"
"<br>Vérifier que l'élément ne possède qu'une seule borne"));
for(auto qgi : m_elmt_scene -> items()) {
if (qgraphicsitem_cast<PartTerminal *>(qgi)) {
terminal ++;
}
}
// Error: Conductor definition must have exactly one terminal
if (terminal != 1) {
errors << qMakePair (tr("Nombre de bornes incorrect"),
tr("<br><b>Erreur</b> :"
"<br>Les définitions de conducteur ne peuvent posséder qu'une seule borne."
"<br><b>Solution</b> :"
"<br>Vérifier que l'élément ne possède qu'une seule borne"));
}
}
if (!errors.count() && !warnings.count()) {
return(true);
}
@@ -1091,7 +1089,7 @@ void QETElementEditor::updateAction()
<< ui->m_revert_selection_action
<< ui->m_paste_from_file_action
<< ui->m_paste_from_element_action;
for (auto action : qAsConst(ro_list)) {
for (auto action : std::as_const(ro_list)) {
action->setDisabled(m_read_only);
}
@@ -1106,7 +1104,7 @@ void QETElementEditor::updateAction()
<< ui->m_flip_action
<< ui->m_mirror_action;
auto items_selected = !m_read_only && m_elmt_scene->selectedItems().count();
for (auto action : qAsConst(select_list)) {
for (auto action : std::as_const(select_list)) {
action->setEnabled(items_selected);
}
@@ -1190,6 +1188,12 @@ void QETElementEditor::initGui()
updateInformations();
fillPartsList();
// When the element type changes, update the terminal editor master label visibility
connect(m_elmt_scene, &ElementScene::elementTypeChanged, this, [this]() {
auto *te = static_cast<TerminalEditor *>(m_editors["terminal"]);
if (te) te->refreshMasterLabelVisibility();
});
statusBar()->showMessage(tr("Éditeur d'éléments", "status bar message"));
}
+333 -1
View File
@@ -21,6 +21,12 @@
#include "../../qet.h"
#include "../graphicspart/partterminal.h"
#include "../../QPropertyUndoCommand/qpropertyundocommand.h"
#include "../../ui/alignmenttextdialog.h"
#include <QColorDialog>
#include <QFontDialog>
#include "../elementscene.h"
#include "qetelementeditor.h"
/**
* @brief TerminalEditor::TerminalEditor
@@ -33,6 +39,22 @@ TerminalEditor::TerminalEditor(QETElementEditor *editor, QWidget *parent) :
ui(new Ui::TerminalEditor)
{
ui->setupUi(this);
#ifdef BUILD_WITHOUT_KF5
m_color_pb = new QPushButton(this);
m_color_pb->setMinimumSize(40, 24);
connect(m_color_pb, &QPushButton::clicked, this, &TerminalEditor::labelColorClicked);
#else
m_color_pb = new KColorButton(this);
m_color_pb->setMinimumSize(40, 24);
connect(m_color_pb, &KColorButton::changed, this, &TerminalEditor::labelColorClicked);
#endif
QLayout *layout = ui->m_color_widget->parentWidget()->layout();
layout->replaceWidget(ui->m_color_widget, m_color_pb);
delete ui->m_color_widget;
ui->m_color_widget = nullptr;
init();
}
@@ -63,6 +85,44 @@ void TerminalEditor::updateForm()
ui->m_name_le->setText(m_part->terminalName());
ui->m_type_cb->setCurrentIndex(ui->m_type_cb->findData(m_part->terminalType()));
ui->m_show_name_cb->setChecked(m_part->showName());
ui->m_label_x_dsb->setValue(m_part->labelPos().x());
ui->m_label_y_dsb->setValue(m_part->labelPos().y());
ui->m_font_pb->setText(m_part->labelFont().family());
ui->m_label_size_sb->setValue(m_part->labelFont().pointSize());
ui->m_label_rotation_sb->setValue(static_cast<int>(m_part->labelRotation()));
ui->m_label_frame_cb->setChecked(m_part->labelFrame());
#ifdef BUILD_WITHOUT_KF5
QPixmap px(16, 16);
px.fill(m_part->labelColor());
m_color_pb->setIcon(QIcon(px));
#else
m_color_pb->setColor(m_part->labelColor());
#endif
ui->m_text_props_gb->setEnabled(m_part->showName());
// Update master label fields
bool is_slave = updateMasterLabelVisibility();
if (is_slave) {
PartTerminal *pt = m_part;
if (pt) {
ui->m_use_master_label_cb->setChecked(pt->useMasterLabel());
ui->m_master_label_cb->setEnabled(pt->useMasterLabel());
ui->m_name_le->setEnabled(!pt->useMasterLabel());
int idx = ui->m_master_label_cb->findData(pt->masterLabelIndex());
if (idx >= 0) {
ui->m_master_label_cb->setCurrentIndex(idx);
}
// Show T-label in name field when master label is active
if (pt->useMasterLabel()) {
int label_idx = pt->masterLabelIndex();
ui->m_name_le->setText(tr("T%1").arg(label_idx + 1));
}
}
}
activeConnections(true);
}
@@ -125,6 +185,16 @@ void TerminalEditor::init()
ui->m_type_cb->addItem(tr("NO (contact SW)"), TerminalData::No);
ui->m_type_cb->addItem(tr("NC (contact SW)"), TerminalData::Nc);
ui->m_type_cb->addItem(tr("Commun (contact SW)"), TerminalData::Common);
ui->m_text_props_gb->setEnabled(false);
// Populate master label dropdown (T1-T20)
for (int i = 1; i <= 20; ++i) {
ui->m_master_label_cb->addItem(tr("T%1").arg(i), i - 1);
}
// Check if parent element is a Slave to show/hide master label group
updateMasterLabelVisibility();
}
/**
@@ -218,6 +288,146 @@ void TerminalEditor::typeEdited()
* and method of this class.
* @param active
*/
void TerminalEditor::showNameEdited()
{
if (m_locked) return;
m_locked = true;
bool show = ui->m_show_name_cb->isChecked();
if (m_part->showName() != show) {
auto undo = new QPropertyUndoCommand(m_part, "show_name", m_part->showName(), show);
undo->setText(tr("Afficher/cacher le nom du terminal"));
undoStack().push(undo);
}
ui->m_text_props_gb->setEnabled(show);
m_locked = false;
}
void TerminalEditor::labelPosEdited()
{
if (m_locked) return;
m_locked = true;
QPointF new_pos(ui->m_label_x_dsb->value(), ui->m_label_y_dsb->value());
if (m_part->labelPos() != new_pos) {
auto undo = new QPropertyUndoCommand(m_part, "label_pos", m_part->labelPos(), new_pos);
undo->setText(tr("Modifier la position du label"));
undoStack().push(undo);
}
m_locked = false;
}
void TerminalEditor::labelFontClicked()
{
if (m_locked) return;
m_locked = true;
bool ok;
QFont font = QFontDialog::getFont(&ok, m_part->labelFont(), this);
if (ok && font != m_part->labelFont()) {
ui->m_font_pb->setText(font.family());
ui->m_label_size_sb->blockSignals(true);
ui->m_label_size_sb->setValue(font.pointSize());
ui->m_label_size_sb->blockSignals(false);
auto undo = new QPropertyUndoCommand(m_part, "label_font", m_part->labelFont(), font);
undo->setText(tr("Modifier la police du label"));
undoStack().push(undo);
}
m_locked = false;
}
void TerminalEditor::labelSizeEdited()
{
if (m_locked) return;
m_locked = true;
QFont new_font = m_part->labelFont();
new_font.setPointSize(ui->m_label_size_sb->value());
if (m_part->labelFont() != new_font) {
auto undo = new QPropertyUndoCommand(m_part, "label_font", m_part->labelFont(), new_font);
undo->setText(tr("Modifier la taille de police du label"));
undoStack().push(undo);
}
m_locked = false;
}
void TerminalEditor::labelRotationEdited()
{
if (m_locked) return;
m_locked = true;
qreal rot = static_cast<qreal>(ui->m_label_rotation_sb->value());
if (!qFuzzyCompare(m_part->labelRotation(), rot)) {
auto undo = new QPropertyUndoCommand(m_part, "label_rotation", m_part->labelRotation(), rot);
undo->setText(tr("Modifier la rotation du label"));
undoStack().push(undo);
}
m_locked = false;
}
void TerminalEditor::labelAlignClicked()
{
Qt::Alignment align = m_part->labelHAlignment() | m_part->labelVAlignment();
AlignmentTextDialog dialog(align, this);
if (dialog.exec() == QDialog::Accepted) {
Qt::Alignment new_align = dialog.alignment();
Qt::Alignment new_h = new_align & Qt::AlignHorizontal_Mask;
Qt::Alignment new_v = new_align & Qt::AlignVertical_Mask;
if (new_h != m_part->labelHAlignment()) {
auto undo = new QPropertyUndoCommand(m_part, "label_halignment",
QVariant::fromValue(m_part->labelHAlignment()), QVariant::fromValue(new_h));
undo->setText(tr("Modifier l'alignement du label"));
undoStack().push(undo);
}
if (new_v != m_part->labelVAlignment()) {
auto undo = new QPropertyUndoCommand(m_part, "label_valignment",
QVariant::fromValue(m_part->labelVAlignment()), QVariant::fromValue(new_v));
undo->setText(tr("Modifier l'alignement du label"));
undoStack().push(undo);
}
}
}
void TerminalEditor::labelFrameEdited()
{
if (m_locked) return;
m_locked = true;
bool frame = ui->m_label_frame_cb->isChecked();
if (m_part->labelFrame() != frame) {
auto undo = new QPropertyUndoCommand(m_part, "label_frame", m_part->labelFrame(), frame);
undo->setText(tr("Afficher/cacher le cadre du label"));
undoStack().push(undo);
}
m_locked = false;
}
void TerminalEditor::labelColorClicked()
{
if (m_locked) return;
m_locked = true;
#ifdef BUILD_WITHOUT_KF5
QColor new_color = QColorDialog::getColor(m_part->labelColor(), this);
if (new_color.isValid() && m_part->labelColor() != new_color) {
auto undo = new QPropertyUndoCommand(m_part, "label_color", m_part->labelColor(), new_color);
undo->setText(tr("Modifier la couleur du label"));
undoStack().push(undo);
}
#else
QColor new_color = m_color_pb->color();
if (new_color.isValid() && m_part->labelColor() != new_color) {
auto undo = new QPropertyUndoCommand(m_part, "label_color", m_part->labelColor(), new_color);
undo->setText(tr("Modifier la couleur du label"));
undoStack().push(undo);
}
#endif
m_locked = false;
}
void TerminalEditor::activeConnections(bool active)
{
if (active) {
@@ -231,8 +441,28 @@ void TerminalEditor::activeConnections(bool active)
this, &TerminalEditor::nameEdited);
m_editor_connections << connect(ui->m_type_cb, QOverload<int>::of(&QComboBox::activated),
this, &TerminalEditor::typeEdited);
m_editor_connections << connect(ui->m_show_name_cb, &QCheckBox::toggled,
this, &TerminalEditor::showNameEdited);
m_editor_connections << connect(ui->m_label_x_dsb, QOverload<qreal>::of(&QDoubleSpinBox::valueChanged),
[this]() { TerminalEditor::labelPosEdited(); ui->m_label_x_dsb->setFocus(); });
m_editor_connections << connect(ui->m_label_y_dsb, QOverload<qreal>::of(&QDoubleSpinBox::valueChanged),
[this]() { TerminalEditor::labelPosEdited(); ui->m_label_y_dsb->setFocus(); });
m_editor_connections << connect(ui->m_font_pb, &QPushButton::clicked,
this, &TerminalEditor::labelFontClicked);
m_editor_connections << connect(ui->m_label_size_sb, QOverload<int>::of(&QSpinBox::valueChanged),
[this]() { TerminalEditor::labelSizeEdited(); ui->m_label_size_sb->setFocus(); });
m_editor_connections << connect(ui->m_label_rotation_sb, QOverload<double>::of(&QDoubleSpinBox::valueChanged),
[this]() { TerminalEditor::labelRotationEdited(); ui->m_label_rotation_sb->setFocus(); });
m_editor_connections << connect(ui->m_align_pb, &QPushButton::clicked,
this, &TerminalEditor::labelAlignClicked);
m_editor_connections << connect(ui->m_label_frame_cb, &QCheckBox::toggled,
this, &TerminalEditor::labelFrameEdited);
m_editor_connections << connect(ui->m_use_master_label_cb, &QCheckBox::toggled,
this, &TerminalEditor::useMasterLabelEdited);
m_editor_connections << connect(ui->m_master_label_cb, QOverload<int>::of(&QComboBox::activated),
this, &TerminalEditor::masterLabelIndexEdited);
} else {
for (auto const & con : qAsConst(m_editor_connections)) {
for (auto const & con : std::as_const(m_editor_connections)) {
QObject::disconnect(con);
}
m_editor_connections.clear();
@@ -248,6 +478,16 @@ void TerminalEditor::activeChangeConnections(bool active)
m_change_connections << connect(m_part, &PartTerminal::orientationChanged, this, &TerminalEditor::updateForm);
m_change_connections << connect(m_part, &PartTerminal::nameChanged, this, &TerminalEditor::updateForm);
m_change_connections << connect(m_part, &PartTerminal::terminalTypeChanged, this, &TerminalEditor::updateForm);
m_change_connections << connect(m_part, &PartTerminal::showNameChanged, this, &TerminalEditor::updateForm);
m_change_connections << connect(m_part, &PartTerminal::labelPosChanged, this, &TerminalEditor::updateForm);
m_change_connections << connect(m_part, &PartTerminal::labelFontChanged, this, &TerminalEditor::updateForm);
m_change_connections << connect(m_part, &PartTerminal::labelRotationChanged, this, &TerminalEditor::updateForm);
m_change_connections << connect(m_part, &PartTerminal::labelHAlignmentChanged, this, &TerminalEditor::updateForm);
m_change_connections << connect(m_part, &PartTerminal::labelVAlignmentChanged, this, &TerminalEditor::updateForm);
m_change_connections << connect(m_part, &PartTerminal::labelFrameChanged, this, &TerminalEditor::updateForm);
m_change_connections << connect(m_part, &PartTerminal::labelColorChanged, this, &TerminalEditor::updateForm);
m_change_connections << connect(m_part, &PartTerminal::useMasterLabelChanged, this, &TerminalEditor::updateForm);
m_change_connections << connect(m_part, &PartTerminal::masterLabelIndexChanged, this, &TerminalEditor::updateForm);
} else {
for (auto &con : m_change_connections) {
QObject::disconnect(con);
@@ -255,3 +495,95 @@ void TerminalEditor::activeChangeConnections(bool active)
m_change_connections.clear();
}
}
void TerminalEditor::useMasterLabelEdited()
{
if (m_locked) return;
m_locked = true;
bool use = ui->m_use_master_label_cb->isChecked();
ui->m_master_label_cb->setEnabled(use);
QSignalBlocker name_blocker(ui->m_name_le);
if (m_part->useMasterLabel() != use) {
auto undo = new QPropertyUndoCommand(m_part, "use_master_label",
m_part->useMasterLabel(), use);
undo->setText(tr("Modifier l'étiquette du maître"));
undoStack().push(undo);
}
if (use) {
int idx = ui->m_master_label_cb->currentData().toInt();
QString t_label = tr("T%1").arg(idx + 1);
ui->m_name_le->setText(t_label);
ui->m_name_le->setEnabled(false);
if (m_part->terminalName() != t_label) {
auto undo = new QPropertyUndoCommand(m_part, "terminal_name",
m_part->terminalName(), t_label);
undo->setText(tr("Modifier le nom de la borne"));
undoStack().push(undo);
}
} else {
ui->m_name_le->setEnabled(true);
if (!m_part->terminalName().isEmpty()) {
auto undo = new QPropertyUndoCommand(m_part, "terminal_name",
m_part->terminalName(), QString());
undo->setText(tr("Modifier le nom de la borne"));
undoStack().push(undo);
}
ui->m_name_le->clear();
}
m_locked = false;
}
void TerminalEditor::masterLabelIndexEdited()
{
if (m_locked) return;
m_locked = true;
int idx = ui->m_master_label_cb->currentData().toInt();
if (m_part->masterLabelIndex() != idx) {
auto undo = new QPropertyUndoCommand(m_part, "master_label_index",
m_part->masterLabelIndex(), idx);
undo->setText(tr("Modifier l'index de l'étiquette du maître"));
undoStack().push(undo);
}
if (ui->m_use_master_label_cb->isChecked()) {
QString t_label = tr("T%1").arg(idx + 1);
ui->m_name_le->setText(t_label);
if (m_part->terminalName() != t_label) {
auto undo = new QPropertyUndoCommand(m_part, "terminal_name",
m_part->terminalName(), t_label);
undo->setText(tr("Modifier le nom de la borne"));
undoStack().push(undo);
}
}
m_locked = false;
}
bool TerminalEditor::updateMasterLabelVisibility()
{
QETElementEditor *editor = elementEditor();
if (!editor || !editor->elementScene()) {
ui->m_master_label_gb->setVisible(false);
return false;
}
ElementData data = editor->elementScene()->elementData();
bool is_slave = (data.m_type == ElementData::Slave);
ui->m_master_label_gb->setVisible(is_slave);
return is_slave;
}
void TerminalEditor::refreshMasterLabelVisibility()
{
updateMasterLabelVisibility();
if (m_part) {
updateForm();
}
}
+26 -2
View File
@@ -21,6 +21,12 @@
#include <QWidget>
#include "../elementitemeditor.h"
#ifdef BUILD_WITHOUT_KF5
#include <QPushButton>
#else
#include <KColorButton>
#endif
namespace Ui {
class TerminalEditor;
}
@@ -43,6 +49,8 @@ class TerminalEditor : public ElementItemEditor
bool setPart(CustomElementPart *new_part) override;
CustomElementPart *currentPart() const override;
QList<CustomElementPart *> currentParts() const override {return QList<CustomElementPart *>();}
public slots:
void refreshMasterLabelVisibility();
private:
void init();
@@ -50,8 +58,19 @@ class TerminalEditor : public ElementItemEditor
void orientationEdited();
void nameEdited();
void typeEdited();
void activeConnections(bool active);
void activeChangeConnections(bool active);
void showNameEdited();
void labelPosEdited();
void labelFontClicked();
void labelSizeEdited();
void labelRotationEdited();
void labelAlignClicked();
void labelFrameEdited();
void labelColorClicked();
void activeConnections(bool active);
void activeChangeConnections(bool active);
void useMasterLabelEdited();
void masterLabelIndexEdited();
bool updateMasterLabelVisibility();
private:
Ui::TerminalEditor *ui;
@@ -59,6 +78,11 @@ class TerminalEditor : public ElementItemEditor
m_change_connections;
PartTerminal *m_part = nullptr;
bool m_locked = false;
#ifdef BUILD_WITHOUT_KF5
QPushButton *m_color_pb;
#else
KColorButton *m_color_pb;
#endif
};
#endif // TERMINALEDITOR_H
+160 -11
View File
@@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>511</width>
<height>236</height>
<height>500</height>
</rect>
</property>
<property name="windowTitle">
@@ -78,18 +78,167 @@
<item row="3" column="1">
<widget class="QComboBox" name="m_type_cb"/>
</item>
<item row="5" column="1">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
<item row="5" column="0" colspan="2">
<widget class="QGroupBox" name="m_label_gb">
<property name="title">
<string>Nom de la borne</string>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QCheckBox" name="m_show_name_cb">
<property name="text">
<string>Afficher le nom</string>
</property>
</widget>
</item>
<item>
<widget class="QGroupBox" name="m_text_props_gb">
<property name="title">
<string>Propriétés du texte</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<widget class="QPushButton" name="m_font_pb">
<property name="text">
<string>Police</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QSpinBox" name="m_label_size_sb">
<property name="minimum">
<number>4</number>
</property>
<property name="maximum">
<number>50</number>
</property>
<property name="value">
<number>9</number>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_7">
<property name="text">
<string>X :</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QDoubleSpinBox" name="m_label_x_dsb">
<property name="minimum">
<double>-5000.000000000000000</double>
</property>
<property name="maximum">
<double>5000.000000000000000</double>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_8">
<property name="text">
<string>Y :</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QDoubleSpinBox" name="m_label_y_dsb">
<property name="minimum">
<double>-5000.000000000000000</double>
</property>
<property name="maximum">
<double>5000.000000000000000</double>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_10">
<property name="text">
<string>Rotation :</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QDoubleSpinBox" name="m_label_rotation_sb">
<property name="wrapping">
<bool>true</bool>
</property>
<property name="suffix">
<string>°</string>
</property>
<property name="decimals">
<number>0</number>
</property>
<property name="minimum">
<double>0.000000000000000</double>
</property>
<property name="maximum">
<double>359.000000000000000</double>
</property>
</widget>
</item>
<item row="4" column="0" colspan="2">
<widget class="QPushButton" name="m_align_pb">
<property name="text">
<string>Alignement</string>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_13">
<property name="text">
<string>Couleur :</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QWidget" name="m_color_widget" native="true">
<property name="minimumSize">
<size>
<width>40</width>
<height>24</height>
</size>
</property>
</widget>
</item>
<item row="6" column="0" colspan="2">
<widget class="QCheckBox" name="m_label_frame_cb">
<property name="text">
<string>Encadrer le texte</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
<item row="6" column="0" colspan="2">
<widget class="QGroupBox" name="m_master_label_gb">
<property name="title">
<string>Étiquette du maître</string>
</property>
</spacer>
<property name="visible">
<bool>false</bool>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="0">
<widget class="QCheckBox" name="m_use_master_label_cb">
<property name="text">
<string>Reprendre du maître</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="m_master_label_cb">
<property name="enabled">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
+29 -2
View File
@@ -18,6 +18,7 @@
#include "texteditor.h"
#include "../../QPropertyUndoCommand/qpropertyundocommand.h"
#include "../../ui/alignmenttextdialog.h"
#include "../graphicspart/parttext.h"
#include <cassert>
@@ -84,7 +85,7 @@ void TextEditor::setUpChangeConnection(QPointer<PartText> part)
void TextEditor::disconnectChangeConnection()
{
for (const auto &connection : qAsConst(m_change_connection)) {
for (const auto &connection : std::as_const(m_change_connection)) {
disconnect(connection);
}
m_change_connection.clear();
@@ -371,8 +372,34 @@ void TextEditor::setUpWidget(QWidget *parent)
gridLayout->addWidget(m_font_pb, 2, 2, 1, 2);
QPushButton *alignment_pb = new QPushButton(tr("Alignement"), parent);
alignment_pb->setToolTip(tr("Point d'ancrage du texte et alignement"
" des lignes entre elles"));
connect(alignment_pb, &QPushButton::clicked, [this]() {
if (m_text.isNull()) {
return;
}
AlignmentTextDialog atd(m_text->alignment(), this);
if (atd.exec() != QDialog::Accepted) {
return;
}
for (int i = 0; i < m_parts.length(); i++) {
PartText *part_text = m_parts[i];
if (atd.alignment() != part_text->alignment()) {
QPropertyUndoCommand *undo = new QPropertyUndoCommand(
part_text, "alignment",
QVariant(part_text->alignment()),
QVariant(atd.alignment()));
undo->setText(tr("Modifier l'alignement d'un champ texte"));
undoStack().push(undo);
}
}
});
gridLayout->addWidget(alignment_pb, 3, 0, 1, 2);
QSpacerItem *verticalSpacer = new QSpacerItem(20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding);
gridLayout->addItem(verticalSpacer, 3, 2, 1, 1);
gridLayout->addItem(verticalSpacer, 4, 2, 1, 1);
setLayout(gridLayout);
}