|
|
|
@@ -19,8 +19,10 @@
|
|
|
|
|
|
|
|
|
|
#include "../ElementsCollection/elementslocation.h"
|
|
|
|
|
#include "../editor/graphicspart/partline.h"
|
|
|
|
|
#include "../properties/elementdata.h"
|
|
|
|
|
#include "../qetapp.h"
|
|
|
|
|
#include "../qetversion.h"
|
|
|
|
|
#include "../utils/qetutils.h"
|
|
|
|
|
|
|
|
|
|
#include <QAbstractTextDocumentLayout>
|
|
|
|
|
#include <QDomElement>
|
|
|
|
@@ -30,9 +32,37 @@
|
|
|
|
|
#include <QRegularExpression>
|
|
|
|
|
#include <QTextDocument>
|
|
|
|
|
#include <iostream>
|
|
|
|
|
#include <algorithm>
|
|
|
|
|
|
|
|
|
|
ElementPictureFactory* ElementPictureFactory::m_factory = nullptr;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
@brief ElementPictureFactory::cacheKey
|
|
|
|
|
@param location
|
|
|
|
|
@return the key under which the drawing of the element at location is
|
|
|
|
|
cached.
|
|
|
|
|
|
|
|
|
|
An element definition normally carries its own uuid, and that is used
|
|
|
|
|
directly. Definitions saved before uuids were written do not have one --
|
|
|
|
|
a good share of the shipped example projects are still in that state --
|
|
|
|
|
and they all presented the same null uuid as a key. The drawing of such
|
|
|
|
|
an element was therefore either rebuilt for every instance placed, or
|
|
|
|
|
stored under a key it shared with every other element lacking a uuid.
|
|
|
|
|
Derive a stable key from the location for those instead:
|
|
|
|
|
ElementsLocation::toString() qualifies an embedded path with the id of
|
|
|
|
|
the project that owns it, and project ids come from an ever-increasing
|
|
|
|
|
counter and are never reused, so the derived key cannot collide with an
|
|
|
|
|
element of another project.
|
|
|
|
|
*/
|
|
|
|
|
QUuid ElementPictureFactory::cacheKey(const ElementsLocation &location)
|
|
|
|
|
{
|
|
|
|
|
const QUuid uuid = location.uuid();
|
|
|
|
|
if (!uuid.isNull()) {
|
|
|
|
|
return uuid;
|
|
|
|
|
}
|
|
|
|
|
return QUuid::createUuidV5(QUuid(), location.toString().toUtf8());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
@brief ElementPictureFactory::getPictures
|
|
|
|
|
Set the picture of the element at location.
|
|
|
|
@@ -47,14 +77,9 @@ void ElementPictureFactory::getPictures(const ElementsLocation &location, QPictu
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QUuid uuid = location.uuid();
|
|
|
|
|
if(Q_UNLIKELY(uuid.isNull()))
|
|
|
|
|
{
|
|
|
|
|
build(location, &picture, &low_picture);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const QUuid uuid = cacheKey(location);
|
|
|
|
|
|
|
|
|
|
if(m_pictures_H.keys().contains(uuid))
|
|
|
|
|
if(m_pictures_H.contains(uuid))
|
|
|
|
|
{
|
|
|
|
|
picture = m_pictures_H.value(uuid);
|
|
|
|
|
low_picture = m_low_pictures_H.value(uuid);
|
|
|
|
@@ -69,6 +94,29 @@ void ElementPictureFactory::getPictures(const ElementsLocation &location, QPictu
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
@brief ElementPictureFactory::dropCache
|
|
|
|
|
Forget the cached drawing of the element at @p location, so the next
|
|
|
|
|
getPictures()/pixmap()/getPrimitives() call rebuilds it from the
|
|
|
|
|
definition's current content instead of returning what was cached the
|
|
|
|
|
first time this location was drawn.
|
|
|
|
|
|
|
|
|
|
A placed Element keeps its own copy of the picture in m_picture /
|
|
|
|
|
m_low_zoom_picture (set once, in buildFromXml()), so dropping the shared
|
|
|
|
|
cache here does not by itself change what is on screen -- callers doing
|
|
|
|
|
a manual refresh (bugtracker #802) still need each Element to re-fetch
|
|
|
|
|
its picture afterwards.
|
|
|
|
|
@param location
|
|
|
|
|
*/
|
|
|
|
|
void ElementPictureFactory::dropCache(const ElementsLocation &location)
|
|
|
|
|
{
|
|
|
|
|
const QUuid uuid = cacheKey(location);
|
|
|
|
|
m_pictures_H.remove(uuid);
|
|
|
|
|
m_low_pictures_H.remove(uuid);
|
|
|
|
|
m_pixmap_H.remove(uuid);
|
|
|
|
|
m_primitives_H.remove(uuid);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
@brief ElementPictureFactory::pixmap
|
|
|
|
|
@param location
|
|
|
|
@@ -77,7 +125,7 @@ void ElementPictureFactory::getPictures(const ElementsLocation &location, QPictu
|
|
|
|
|
*/
|
|
|
|
|
QPixmap ElementPictureFactory::pixmap(const ElementsLocation &location)
|
|
|
|
|
{
|
|
|
|
|
QUuid uuid = location.uuid();
|
|
|
|
|
const QUuid uuid = cacheKey(location);
|
|
|
|
|
|
|
|
|
|
if (m_pixmap_H.contains(uuid)) {
|
|
|
|
|
return m_pixmap_H.value(uuid);
|
|
|
|
@@ -96,7 +144,15 @@ QPixmap ElementPictureFactory::pixmap(const ElementsLocation &location)
|
|
|
|
|
int hsy = qMin(doc.document_element().attribute("hotspot_y").as_int(), h);
|
|
|
|
|
|
|
|
|
|
QPixmap pix(w, h);
|
|
|
|
|
pix.fill(QColor(255, 255, 255, 0));
|
|
|
|
|
//Element definitions almost always draw with a hardcoded black
|
|
|
|
|
//stroke color, on the assumption of the white diagram sheet they
|
|
|
|
|
//are normally placed on. The pixmap is kept as drawn, on a
|
|
|
|
|
//transparent background: the places that show it (the
|
|
|
|
|
//collection tree through ElementPreviewDelegate, the drag icon)
|
|
|
|
|
//adapt it to the palette with QET::Palette::forPalette(), so a
|
|
|
|
|
//dark palette gets light ink instead of black on black
|
|
|
|
|
//(bugtracker #335).
|
|
|
|
|
pix.fill(Qt::transparent);
|
|
|
|
|
|
|
|
|
|
QPainter painter(&pix);
|
|
|
|
|
painter.setRenderHint(QPainter::Antialiasing, true);
|
|
|
|
@@ -104,9 +160,7 @@ QPixmap ElementPictureFactory::pixmap(const ElementsLocation &location)
|
|
|
|
|
painter.translate(hsx, hsy);
|
|
|
|
|
painter.drawPicture(0, 0, m_pictures_H.value(uuid));
|
|
|
|
|
|
|
|
|
|
if (!uuid.isNull()) {
|
|
|
|
|
m_pixmap_H.insert(uuid, pix);
|
|
|
|
|
}
|
|
|
|
|
m_pixmap_H.insert(uuid, pix);
|
|
|
|
|
return pix;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -122,10 +176,11 @@ QPixmap ElementPictureFactory::pixmap(const ElementsLocation &location)
|
|
|
|
|
ElementPictureFactory::primitives ElementPictureFactory::getPrimitives(
|
|
|
|
|
const ElementsLocation &location)
|
|
|
|
|
{
|
|
|
|
|
if(!m_primitives_H.contains(location.uuid()))
|
|
|
|
|
const QUuid uuid = cacheKey(location);
|
|
|
|
|
if(!m_primitives_H.contains(uuid))
|
|
|
|
|
build(location);
|
|
|
|
|
|
|
|
|
|
return m_primitives_H.value(location.uuid());
|
|
|
|
|
return m_primitives_H.value(uuid);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ElementPictureFactory::~ElementPictureFactory()
|
|
|
|
@@ -206,7 +261,25 @@ bool ElementPictureFactory::build(const ElementsLocation &location,
|
|
|
|
|
tmp.setCosmetic(true);
|
|
|
|
|
low_painter.setPen(tmp);
|
|
|
|
|
|
|
|
|
|
//scroll of the Children of the Definition: Parts of the Drawing
|
|
|
|
|
//scroll of the Children of the Definition: Parts of the Drawing
|
|
|
|
|
// Extract PLC master data for rendering plc_table parts
|
|
|
|
|
QDomElement plc_master_data;
|
|
|
|
|
for (QDomNode node = dom.firstChild() ; !node.isNull() ; node = node.nextSibling())
|
|
|
|
|
{
|
|
|
|
|
QDomElement elmts = node.toElement();
|
|
|
|
|
if (elmts.isNull()) continue;
|
|
|
|
|
if (elmts.tagName() == "kindInformations") {
|
|
|
|
|
for (QDomNode ki = elmts.firstChild(); !ki.isNull(); ki = ki.nextSibling()) {
|
|
|
|
|
QDomElement ki_el = ki.toElement();
|
|
|
|
|
if (!ki_el.isNull() && ki_el.tagName() == "plcMasterData") {
|
|
|
|
|
plc_master_data = ki_el;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (QDomNode node = dom.firstChild() ; !node.isNull() ; node = node.nextSibling())
|
|
|
|
|
{
|
|
|
|
|
QDomElement elmts = node.toElement();
|
|
|
|
@@ -223,9 +296,17 @@ bool ElementPictureFactory::build(const ElementsLocation &location,
|
|
|
|
|
if (qde.isNull()) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
parseElement(qde, painter, primitives_);
|
|
|
|
|
primitives fake_prim;
|
|
|
|
|
parseElement(qde, low_painter, fake_prim);
|
|
|
|
|
if (qde.tagName() == "plc_table") {
|
|
|
|
|
// Skip - PLC table is rendered at runtime by
|
|
|
|
|
// Element::drawPlcTable() from live ElementData.
|
|
|
|
|
// Rendering into the cached QPicture here causes
|
|
|
|
|
// intermittent crashes when drawPicture() replays
|
|
|
|
|
// complex font/text operations.
|
|
|
|
|
} else {
|
|
|
|
|
parseElement(qde, painter, primitives_);
|
|
|
|
|
primitives fake_prim;
|
|
|
|
|
parseElement(qde, low_painter, fake_prim);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@@ -234,7 +315,7 @@ bool ElementPictureFactory::build(const ElementsLocation &location,
|
|
|
|
|
painter.end();
|
|
|
|
|
low_painter.end();
|
|
|
|
|
|
|
|
|
|
const auto uuid_ = location.uuid();
|
|
|
|
|
const auto uuid_ = cacheKey(location);
|
|
|
|
|
if (!picture) {
|
|
|
|
|
m_pictures_H.insert(uuid_, pic);
|
|
|
|
|
m_primitives_H.insert(uuid_, primitives_);
|
|
|
|
@@ -503,7 +584,7 @@ void ElementPictureFactory::parseText(const QDomElement &dom, QPainter &painter,
|
|
|
|
|
font_ = QETApp::diagramTextsFont(dom.attribute("size").toDouble());
|
|
|
|
|
}
|
|
|
|
|
else if (dom.hasAttribute("font")) {
|
|
|
|
|
font_.fromString(dom.attribute("font"));
|
|
|
|
|
QETUtils::fontFromString(font_, dom.attribute("font"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QColor text_color(dom.attribute("color", "#000000"));
|
|
|
|
@@ -534,6 +615,22 @@ void ElementPictureFactory::parseText(const QDomElement &dom, QPainter &painter,
|
|
|
|
|
//adjusts the offset by the margin of the text document
|
|
|
|
|
text_document.setDocumentMargin(0.0);
|
|
|
|
|
|
|
|
|
|
//Optional line alignment of multi-line texts (the anchor behaviour
|
|
|
|
|
//of the alignment is handled in the element editor; the saved x/y
|
|
|
|
|
//always stay the baseline-left of the text block). The document
|
|
|
|
|
//only honors the text option once a text width is set.
|
|
|
|
|
if (dom.hasAttribute("Halignment")) {
|
|
|
|
|
const QMetaEnum me = QMetaEnum::fromType<Qt::Alignment>();
|
|
|
|
|
const Qt::Alignment h_alignment = Qt::Alignment(
|
|
|
|
|
me.keyToValue(dom.attribute("Halignment").toStdString().data()));
|
|
|
|
|
if (h_alignment & (Qt::AlignHCenter | Qt::AlignRight)) {
|
|
|
|
|
QTextOption option = text_document.defaultTextOption();
|
|
|
|
|
option.setAlignment(h_alignment & Qt::AlignHorizontal_Mask);
|
|
|
|
|
text_document.setDefaultTextOption(option);
|
|
|
|
|
text_document.setTextWidth(text_document.idealWidth());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
painter.translate(qpainter_offset);
|
|
|
|
|
|
|
|
|
|
// force the palette used to render the QTextDocument
|
|
|
|
@@ -567,16 +664,13 @@ void ElementPictureFactory::setPainterStyle(const QDomElement &dom, QPainter &pa
|
|
|
|
|
pen.setCapStyle(Qt::SquareCap);
|
|
|
|
|
|
|
|
|
|
//Get the couples style/value
|
|
|
|
|
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) // ### Qt 6: remove
|
|
|
|
|
const QStringList styles = dom.attribute("style").split(";", QString::SkipEmptyParts);
|
|
|
|
|
#else
|
|
|
|
|
#if TODO_LIST
|
|
|
|
|
#pragma message("@TODO remove code for QT 5.14 or later")
|
|
|
|
|
#endif
|
|
|
|
|
const QStringList styles = dom.attribute("style").split(";", Qt::SkipEmptyParts);
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
QRegularExpression rx("^(?<name>[a-z-]+):(?<value>[a-zA-Z-]+)$");
|
|
|
|
|
//Built once : this runs for every primitive of every element
|
|
|
|
|
//instance a project places, and recompiling the pattern each time
|
|
|
|
|
//was the single largest cost of opening a project.
|
|
|
|
|
static const QRegularExpression rx(
|
|
|
|
|
QStringLiteral("^(?<name>[a-z-]+):(?<value>[a-zA-Z-]+)$"));
|
|
|
|
|
if (!rx.isValid())
|
|
|
|
|
{
|
|
|
|
|
qWarning() <<QObject::tr("this is an error in the code")
|
|
|
|
@@ -1088,3 +1182,217 @@ void ElementPictureFactory::setPainterStyle(const QDomElement &dom, QPainter &pa
|
|
|
|
|
painter.setPen(pen);
|
|
|
|
|
painter.setBrush(brush);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ElementPictureFactory::parsePlcTable(const QDomElement &dom, const QDomElement &plc_data, QPainter &painter) const
|
|
|
|
|
{
|
|
|
|
|
qreal pos_x = dom.attribute("x", "0").toDouble();
|
|
|
|
|
qreal pos_y = dom.attribute("y", "0").toDouble();
|
|
|
|
|
|
|
|
|
|
if (plc_data.isNull()) return;
|
|
|
|
|
|
|
|
|
|
const int COL_COUNT = 5;
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
|
|
qreal rowHeight = plc_data.attribute("rowHeight", "8.0").toDouble();
|
|
|
|
|
|
|
|
|
|
// Parse showHeaders
|
|
|
|
|
bool showHeaders = true;
|
|
|
|
|
auto xml_sh = plc_data.firstChildElement("showHeaders");
|
|
|
|
|
if (!xml_sh.isNull())
|
|
|
|
|
showHeaders = (xml_sh.text().trimmed() != "0");
|
|
|
|
|
|
|
|
|
|
qreal header_h = showHeaders ? (rowHeight + 2.0) : 0;
|
|
|
|
|
|
|
|
|
|
// Parse column widths
|
|
|
|
|
QMap<int, qreal> col_widths;
|
|
|
|
|
auto xml_cw = plc_data.firstChildElement("columnWidths");
|
|
|
|
|
for (auto xml_col = xml_cw.firstChildElement("column"); !xml_col.isNull();
|
|
|
|
|
xml_col = xml_col.nextSiblingElement("column")) {
|
|
|
|
|
int idx = xml_col.attribute("index").toInt();
|
|
|
|
|
qreal w = xml_col.attribute("width").toDouble();
|
|
|
|
|
if (w > 0) col_widths[idx] = w;
|
|
|
|
|
}
|
|
|
|
|
if (col_widths.isEmpty()) {
|
|
|
|
|
col_widths[0] = 35; col_widths[1] = 25; col_widths[2] = 50;
|
|
|
|
|
col_widths[3] = 40; col_widths[4] = 30;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Parse column visibility
|
|
|
|
|
QMap<int, bool> col_visible;
|
|
|
|
|
auto xml_cv = plc_data.firstChildElement("columnVisibility");
|
|
|
|
|
for (auto xml_c = xml_cv.firstChildElement("column"); !xml_c.isNull();
|
|
|
|
|
xml_c = xml_c.nextSiblingElement("column")) {
|
|
|
|
|
int idx = xml_c.attribute("index").toInt();
|
|
|
|
|
QString vis = xml_c.attribute("visible", "true");
|
|
|
|
|
col_visible[idx] = (vis == "true");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Parse column order
|
|
|
|
|
QList<int> column_order;
|
|
|
|
|
auto xml_ord = plc_data.firstChildElement("columnOrder");
|
|
|
|
|
if (!xml_ord.isNull()) {
|
|
|
|
|
QStringList parts = xml_ord.text().split(",", Qt::SkipEmptyParts);
|
|
|
|
|
for (const QString &s : parts) {
|
|
|
|
|
int idx = s.trimmed().toInt();
|
|
|
|
|
if (idx >= 0 && idx < COL_COUNT)
|
|
|
|
|
column_order.append(idx);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Parse custom column names
|
|
|
|
|
QMap<int, QString> column_names;
|
|
|
|
|
auto xml_names = plc_data.firstChildElement("columnNames");
|
|
|
|
|
for (auto xml_n = xml_names.firstChildElement("column"); !xml_n.isNull();
|
|
|
|
|
xml_n = xml_n.nextSiblingElement("column")) {
|
|
|
|
|
int idx = xml_n.attribute("index").toInt();
|
|
|
|
|
QString name = xml_n.text().trimmed();
|
|
|
|
|
if (!name.isEmpty())
|
|
|
|
|
column_names[idx] = name;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Parse fonts
|
|
|
|
|
QFont header_font;
|
|
|
|
|
header_font.setPointSize(7);
|
|
|
|
|
header_font.setBold(true);
|
|
|
|
|
auto xml_hfont = plc_data.firstChildElement("headerFont");
|
|
|
|
|
if (!xml_hfont.isNull()) {
|
|
|
|
|
if (!xml_hfont.attribute("family").isEmpty())
|
|
|
|
|
header_font.setFamily(xml_hfont.attribute("family"));
|
|
|
|
|
if (xml_hfont.attribute("size").toInt() > 0)
|
|
|
|
|
header_font.setPointSize(xml_hfont.attribute("size").toInt());
|
|
|
|
|
header_font.setBold(xml_hfont.attribute("bold") == "true");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QFont cell_font;
|
|
|
|
|
cell_font.setPointSize(7);
|
|
|
|
|
auto xml_cfont = plc_data.firstChildElement("cellFont");
|
|
|
|
|
if (!xml_cfont.isNull()) {
|
|
|
|
|
if (!xml_cfont.attribute("family").isEmpty())
|
|
|
|
|
cell_font.setFamily(xml_cfont.attribute("family"));
|
|
|
|
|
if (xml_cfont.attribute("size").toInt() > 0)
|
|
|
|
|
cell_font.setPointSize(xml_cfont.attribute("size").toInt());
|
|
|
|
|
cell_font.setBold(xml_cfont.attribute("bold") == "true");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Parse IOs
|
|
|
|
|
QVector<QMap<int, QString>> ios;
|
|
|
|
|
auto xml_ios = plc_data.firstChildElement("plcIOs");
|
|
|
|
|
for (auto xml_io = xml_ios.firstChildElement("plcIO"); !xml_io.isNull();
|
|
|
|
|
xml_io = xml_io.nextSiblingElement("plcIO")) {
|
|
|
|
|
QMap<int, QString> io_data;
|
|
|
|
|
io_data[COL_TYPE] = ElementData::translatedPlcIOType(
|
|
|
|
|
ElementData::plcIOTypeFromString(xml_io.attribute("type")));
|
|
|
|
|
io_data[COL_ADDRESS] = xml_io.attribute("address");
|
|
|
|
|
io_data[COL_FUNCTION] = xml_io.attribute("functionText");
|
|
|
|
|
io_data[COL_COMMENT] = xml_io.attribute("comment");
|
|
|
|
|
io_data[COL_CROSSREF] = xml_io.attribute("crossRef");
|
|
|
|
|
ios.append(io_data);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Build visible columns (respect column order)
|
|
|
|
|
QList<int> visible_cols;
|
|
|
|
|
if (!column_order.isEmpty()) {
|
|
|
|
|
for (int logical : column_order) {
|
|
|
|
|
if (logical >= 0 && logical < COL_COUNT
|
|
|
|
|
&& col_visible.value(logical, true)
|
|
|
|
|
&& !visible_cols.contains(logical))
|
|
|
|
|
visible_cols.append(logical);
|
|
|
|
|
}
|
|
|
|
|
for (int i = 0; i < COL_COUNT; ++i) {
|
|
|
|
|
if (col_visible.value(i, true) && !visible_cols.contains(i))
|
|
|
|
|
visible_cols.append(i);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
for (int i = 0; i < COL_COUNT; ++i) {
|
|
|
|
|
if (col_visible.value(i, true))
|
|
|
|
|
visible_cols.append(i);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (visible_cols.isEmpty())
|
|
|
|
|
visible_cols << COL_TYPE << COL_ADDRESS << COL_FUNCTION;
|
|
|
|
|
|
|
|
|
|
// Build header labels
|
|
|
|
|
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");
|
|
|
|
|
for (auto it = column_names.constBegin(); it != column_names.constEnd(); ++it)
|
|
|
|
|
headers[it.key()] = it.value();
|
|
|
|
|
|
|
|
|
|
// Parse break positions
|
|
|
|
|
QList<int> breaks;
|
|
|
|
|
auto xml_breaks = plc_data.firstChildElement("breakPositions");
|
|
|
|
|
for (auto xml_bp = xml_breaks.firstChildElement("break"); !xml_bp.isNull();
|
|
|
|
|
xml_bp = xml_bp.nextSiblingElement("break")) {
|
|
|
|
|
int bp = xml_bp.text().toInt();
|
|
|
|
|
if (bp > 0 && bp < ios.size() && !breaks.contains(bp))
|
|
|
|
|
breaks.append(bp);
|
|
|
|
|
}
|
|
|
|
|
std::sort(breaks.begin(), breaks.end());
|
|
|
|
|
|
|
|
|
|
QList<int> block_starts;
|
|
|
|
|
block_starts.append(0);
|
|
|
|
|
for (int bp : breaks)
|
|
|
|
|
block_starts.append(bp);
|
|
|
|
|
int block_count = block_starts.size();
|
|
|
|
|
|
|
|
|
|
qreal total_width = 0;
|
|
|
|
|
for (int col : visible_cols)
|
|
|
|
|
total_width += col_widths.value(col, 30);
|
|
|
|
|
|
|
|
|
|
QPen border_pen(Qt::black, 0.5);
|
|
|
|
|
|
|
|
|
|
int total_ios = ios.size();
|
|
|
|
|
|
|
|
|
|
for (int block = 0; block < block_count; ++block) {
|
|
|
|
|
qreal bx = pos_x + block * (total_width + 3);
|
|
|
|
|
qreal cx = bx;
|
|
|
|
|
|
|
|
|
|
// Draw headers
|
|
|
|
|
if (showHeaders) {
|
|
|
|
|
painter.fillRect(QRectF(cx, pos_y, total_width, header_h), QColor(220, 220, 220));
|
|
|
|
|
painter.setPen(border_pen);
|
|
|
|
|
painter.setBrush(Qt::NoBrush);
|
|
|
|
|
painter.setFont(header_font);
|
|
|
|
|
for (int col : visible_cols) {
|
|
|
|
|
QRectF hr(cx, pos_y, col_widths.value(col, 30), header_h);
|
|
|
|
|
painter.drawRect(hr);
|
|
|
|
|
QRectF text_rect = hr.adjusted(1, 0, -1, 0);
|
|
|
|
|
painter.drawText(text_rect, Qt::AlignCenter, headers.value(col, QString()));
|
|
|
|
|
cx += col_widths.value(col, 30);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Draw IO rows
|
|
|
|
|
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;
|
|
|
|
|
if (io_idx >= ios.size()) break;
|
|
|
|
|
const auto &io = ios.at(io_idx);
|
|
|
|
|
qreal ry = pos_y + header_h + row * rowHeight;
|
|
|
|
|
cx = bx;
|
|
|
|
|
|
|
|
|
|
for (int col : visible_cols) {
|
|
|
|
|
QRectF cr(cx, ry, col_widths.value(col, 30), rowHeight);
|
|
|
|
|
painter.setPen(border_pen);
|
|
|
|
|
painter.setBrush(Qt::NoBrush);
|
|
|
|
|
painter.drawRect(cr);
|
|
|
|
|
|
|
|
|
|
QString cell_text = io.value(col, QString());
|
|
|
|
|
QRectF text_rect = cr.adjusted(1, 0, -1, 0);
|
|
|
|
|
painter.drawText(text_rect, Qt::AlignLeft | Qt::AlignVCenter, cell_text);
|
|
|
|
|
|
|
|
|
|
cx += col_widths.value(col, 30);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|