From e883d2c3bf40002c5ea70f4eae5f6eda6821a4ff Mon Sep 17 00:00:00 2001 From: Laurent Trinques Date: Thu, 15 Oct 2026 14:38:11 +0200 Subject: [PATCH] Improve terminal_strip --- .../GraphicsItem/terminalstripdrawer.cpp | 131 ++++++++- .../GraphicsItem/terminalstripdrawer.h | 8 + .../GraphicsItem/trueterminalstrip.cpp | 15 + .../GraphicsItem/trueterminalstrip.h | 3 + sources/TerminalStrip/realterminal.cpp | 73 +++++ sources/TerminalStrip/realterminal.h | 10 + sources/TerminalStrip/terminalcolor.h | 184 ++++++++++++ sources/TerminalStrip/ui/modelTerminalData.h | 9 + .../TerminalStrip/ui/terminalstripeditor.cpp | 14 +- .../TerminalStrip/ui/terminalstripmodel.cpp | 264 +++++++++++++++++- sources/TerminalStrip/ui/terminalstripmodel.h | 6 + 11 files changed, 708 insertions(+), 9 deletions(-) create mode 100644 sources/TerminalStrip/terminalcolor.h diff --git a/sources/TerminalStrip/GraphicsItem/terminalstripdrawer.cpp b/sources/TerminalStrip/GraphicsItem/terminalstripdrawer.cpp index 7107b152a..fb4040dae 100644 --- a/sources/TerminalStrip/GraphicsItem/terminalstripdrawer.cpp +++ b/sources/TerminalStrip/GraphicsItem/terminalstripdrawer.cpp @@ -18,6 +18,7 @@ #include "terminalstripdrawer.h" #include +#include #include #include @@ -405,6 +406,105 @@ void drawCables(QPainter *painter, const QVector &cells, const Termin } } +/* + * Width and color of a terminal. + */ + +/** + * The width of the terminals of the layout is the width of a terminal + * of 5.2 mm (the usual width of the terminals of 2.5 mm2). + * The width set by the user, in mm, is converted according to this reference. + */ +constexpr qreal reference_terminal_width_mm{5.2}; + +/** + * @brief terminalRect + * @param pattern + * @param index : level of the terminal (0 to 3) + * @param real_t : can be null + * @return The rectangle of the terminal : the one of the layout, + * with the width adapted to the width (in mm) of @a real_t if it is set. + */ +QRectF terminalRect(const TerminalStripLayoutPattern &pattern, + int index, + const QSharedPointer &real_t) +{ + auto rect_{pattern.m_terminal_rect[index]}; + if (real_t && real_t->widthMm() > 0) { + rect_.setWidth(rect_.width() * real_t->widthMm() / reference_terminal_width_mm); + } + return rect_; +} + +/** + * @brief The TerminalColors struct + * Colors used to fill the rectangle of a terminal. + * An invalid fill color means that the rectangle is not filled. + * The stripe color (if valid) is drawn as stripes over the fill color. + */ +struct TerminalColors +{ + QColor fill; + QColor stripe; +}; + +/** + * @brief terminalColors + * @param real_t : can be null + * @return The colors of the terminal : the color set by the user, + * else the default color of the type of the terminal. + */ +TerminalColors terminalColors(const QSharedPointer &real_t) +{ + if (!real_t) { + return {}; + } + if (real_t->color().isValid()) { + return {real_t->color(), real_t->stripeColor()}; + } + switch (real_t->type()) { + case ElementData::TTGround: return {QColor(255, 216, 0), QColor(46, 158, 46)}; //Green/Yellow + default: return {}; + } +} + +/** + * @brief drawStripes + * Draw diagonal stripes of @a color inside @a rect. + */ +void drawStripes(QPainter *painter, const QRectF &rect, const QColor &color) +{ + painter->save(); + painter->setClipRect(rect); + painter->setPen(Qt::NoPen); + painter->setBrush(color); + + const auto band_{std::max(2, rect.width() / 2)}; + for (auto x = rect.left() - rect.height() ; x < rect.right() ; x += 2 * band_) + { + QPolygonF polygon_; + polygon_ << QPointF(x, rect.bottom()) + << QPointF(x + band_, rect.bottom()) + << QPointF(x + band_ + rect.height(), rect.top()) + << QPointF(x + rect.height(), rect.top()); + painter->drawPolygon(polygon_); + } + painter->restore(); +} + +/** + * @return The color (black or white) the more readable on @a background. + * If @a background is invalid, return black. + */ +QColor contrastColor(const QColor &background) +{ + if (!background.isValid()) { + return Qt::black; + } + const auto luminance_{0.299 * background.redF() + 0.587 * background.greenF() + 0.114 * background.blueF()}; + return luminance_ < 0.5 ? QColor(Qt::white) : QColor(Qt::black); +} + } //End anonymous namespace /** @@ -509,7 +609,7 @@ void TerminalStripDrawer::paint(QPainter *painter) break; } - terminal_rect = m_pattern->m_terminal_rect[index_]; + terminal_rect = terminalRect(*m_pattern, index_, real_terminal_vector[i]); //Cable (hose) of this terminal CableCell cable_cell; @@ -523,17 +623,36 @@ void TerminalStripDrawer::paint(QPainter *painter) } const bool have_cable{m_pattern->m_cable_wire_length > 0 && !cable_cell.cable.isEmpty()}; cable_cells.append(cable_cell); - //Draw terminal rect + //Draw terminal rect, filled with the color of the terminal (if any) + const auto colors_{terminalColors(real_terminal_vector[i])}; + const auto fill_color_{colors_.fill}; + const auto text_color_{contrastColor(fill_color_)}; + painter->save(); + if (fill_color_.isValid()) { + painter->setBrush(QBrush(fill_color_)); + } painter->drawRect(terminal_rect); + if (colors_.stripe.isValid()) + { + drawStripes(painter, terminal_rect, colors_.stripe); + painter->setBrush(Qt::NoBrush); + painter->drawRect(terminal_rect); //Draw again the outline, hidden by stripes + } + painter->restore(); //Draw the symbol of the terminal type (fuse, ground, led...) if (m_pattern->m_type_symbol_height > 0 && real_terminal_vector[i]) { + painter->save(); + if (fill_color_.isValid()) { + painter->setPen(text_color_); + } drawTypeSymbol(painter, QRectF{0, m_pattern->m_type_symbol_y, terminal_rect.width(), m_pattern->m_type_symbol_height}, real_terminal_vector[i]->type(), real_terminal_vector[i]->isLed()); + painter->restore(); } //Draw the connections, above and under the terminal @@ -570,6 +689,9 @@ void TerminalStripDrawer::paint(QPainter *painter) //Draw text painter->save(); + if (fill_color_.isValid()) { + painter->setPen(text_color_); + } text_rect.setRect(0, terminals_text_y, terminal_rect.width(), terminals_text_height); if (terminals_text_orientation == Qt::Vertical) { @@ -594,6 +716,9 @@ void TerminalStripDrawer::paint(QPainter *painter) //Draw xref xref_rect.setRect(0, xref_text_y, terminal_rect.width(), xref_text_height); painter->save(); + if (fill_color_.isValid()) { + painter->setPen(text_color_); + } if (xref_text_orientation == Qt::Vertical) { painter->translate(xref_rect.bottomLeft()); @@ -848,7 +973,7 @@ qreal TerminalStripDrawer::width() const break; } - width_ += m_pattern->m_terminal_rect[index_].width(); + width_ += terminalRect(*m_pattern, index_, real_terminal_vector[i]).width(); } } } diff --git a/sources/TerminalStrip/GraphicsItem/terminalstripdrawer.h b/sources/TerminalStrip/GraphicsItem/terminalstripdrawer.h index 68d2eca2c..ca8917e30 100644 --- a/sources/TerminalStrip/GraphicsItem/terminalstripdrawer.h +++ b/sources/TerminalStrip/GraphicsItem/terminalstripdrawer.h @@ -18,6 +18,7 @@ #ifndef TERMINALSTRIPDRAWER_H #define TERMINALSTRIPDRAWER_H +#include #include #include @@ -77,6 +78,13 @@ namespace TerminalStripDrawer virtual QString cable() const { return QString(); } virtual QString cableWire() const { return QString(); } virtual bool isShield() const { return false; } + //Color and width (mm) of the terminal, only used to draw the terminal. + //An invalid color means "no override" : the default color of the type is used. + //A width <= 0 means "unknown" : the default width of the layout is used. + virtual QColor color() const { return QColor(); } + //Second color of a two-tone color (like green/yellow), drawn as stripes over color() + virtual QColor stripeColor() const { return QColor(); } + virtual qreal widthMm() const { return 0; } }; class AbstractPhysicalTerminalInterface diff --git a/sources/TerminalStrip/GraphicsItem/trueterminalstrip.cpp b/sources/TerminalStrip/GraphicsItem/trueterminalstrip.cpp index 4ad3605bf..f96fcc356 100644 --- a/sources/TerminalStrip/GraphicsItem/trueterminalstrip.cpp +++ b/sources/TerminalStrip/GraphicsItem/trueterminalstrip.cpp @@ -153,6 +153,21 @@ namespace TerminalStripDrawer return m_real ? m_real->isShield() : false; } + QColor TrueRealTerminal::color() const + { + return m_real ? m_real->color() : QColor(); + } + + QColor TrueRealTerminal::stripeColor() const + { + return m_real ? m_real->stripeColor() : QColor(); + } + + qreal TrueRealTerminal::widthMm() const + { + return m_real ? m_real->widthMm() : 0; + } + TrueBridge::TrueBridge(QSharedPointer bridge) : m_bridge { bridge } {} diff --git a/sources/TerminalStrip/GraphicsItem/trueterminalstrip.h b/sources/TerminalStrip/GraphicsItem/trueterminalstrip.h index 232246e85..4438eb97f 100644 --- a/sources/TerminalStrip/GraphicsItem/trueterminalstrip.h +++ b/sources/TerminalStrip/GraphicsItem/trueterminalstrip.h @@ -64,6 +64,9 @@ namespace TerminalStripDrawer QString cable() const override; QString cableWire() const override; bool isShield() const override; + QColor color() const override; + QColor stripeColor() const override; + qreal widthMm() const override; private: QSharedPointer m_real; diff --git a/sources/TerminalStrip/realterminal.cpp b/sources/TerminalStrip/realterminal.cpp index e3f4089ee..4ad4bcd54 100644 --- a/sources/TerminalStrip/realterminal.cpp +++ b/sources/TerminalStrip/realterminal.cpp @@ -19,6 +19,7 @@ #include "terminalstrip.h" #include "../qetgraphicsitem/terminalelement.h" #include "physicalterminal.h" +#include "terminalcolor.h" #include "../qetgraphicsitem/conductor.h" /** @@ -191,6 +192,78 @@ bool RealTerminal::isShield() const { return false; } +/** + * @brief RealTerminal::color + * @return The color set by the user for this terminal, drawn in the + * terminal strip. An invalid color mean that the user did not set a color. + * The value is stored in the informations of the element. + * @sa stripeColor + */ +QColor RealTerminal::color() const +{ + QColor main_, stripe_; + if (m_element) { + TerminalColor::parse(m_element->elementData().m_informations.value(colorInfoKey()).toString(), + &main_, &stripe_); + } + return main_; +} + +/** + * @brief RealTerminal::stripeColor + * @return The second color of a two-tone color (green/yellow for example) + * or an invalid color if the color of this terminal is plain or not set. + */ +QColor RealTerminal::stripeColor() const +{ + QColor main_, stripe_; + if (m_element) { + TerminalColor::parse(m_element->elementData().m_informations.value(colorInfoKey()).toString(), + &main_, &stripe_); + } + return stripe_; +} + +/** + * @brief RealTerminal::section + * @return The section in mm2 set by the user for this terminal + * or 0 if no section was set. + * The value is stored in the informations of the element, + * with the dot as decimal separator, whatever the locale. + */ +qreal RealTerminal::section() const +{ + if (m_element) + { + bool ok_ = false; + const auto section_ = m_element->elementData().m_informations.value(sectionInfoKey()).toString().toDouble(&ok_); + if (ok_ && section_ > 0) { + return section_; + } + } + return 0; +} + +/** + * @brief RealTerminal::widthMm + * @return The width in mm of this terminal (the width of the terminal + * sold by the manufacturer), set by the user, or 0 if no width was set. + * The value is stored in the informations of the element, + * with the dot as decimal separator, whatever the locale. + */ +qreal RealTerminal::widthMm() const +{ + if (m_element) + { + bool ok_ = false; + const auto width_ = m_element->elementData().m_informations.value(widthInfoKey()).toString().toDouble(&ok_); + if (ok_ && width_ > 0) { + return width_; + } + } + return 0; +} + /** * @brief RealTerminal::conductor * @return diff --git a/sources/TerminalStrip/realterminal.h b/sources/TerminalStrip/realterminal.h index cf34c34ef..b14969464 100644 --- a/sources/TerminalStrip/realterminal.h +++ b/sources/TerminalStrip/realterminal.h @@ -19,6 +19,7 @@ #define REALTERMINAL_H #include +#include #include #include "../properties/elementdata.h" @@ -66,11 +67,20 @@ class RealTerminal QString cable() const; QString cableWire() const; bool isShield() const; + QColor color() const; + QColor stripeColor() const; + qreal section() const; + qreal widthMm() const; //Keys used to store the cable data in the element informations static QString cableInfoKey() { return QStringLiteral("terminal_cable"); } static QString cableWireInfoKey() { return QStringLiteral("terminal_cable_wire"); } static QString shieldInfoKey() { return QStringLiteral("terminal_shield"); } + //Keys used to store the color and the section (mm2) of the terminal + static QString colorInfoKey() { return QStringLiteral("terminal_color"); } + static QString sectionInfoKey() { return QStringLiteral("terminal_section"); } + //Key used to store the width (mm) of the terminal, drawn in the terminal strip + static QString widthInfoKey() { return QStringLiteral("terminal_width"); } QString conductor() const; diff --git a/sources/TerminalStrip/terminalcolor.h b/sources/TerminalStrip/terminalcolor.h new file mode 100644 index 000000000..4d1601d99 --- /dev/null +++ b/sources/TerminalStrip/terminalcolor.h @@ -0,0 +1,184 @@ +/* + 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 . +*/ +#ifndef TERMINALCOLOR_H +#define TERMINALCOLOR_H + +#include +#include +#include +#include +#include +#include +#include + +/** + * Colors of the terminals of a terminal strip. + * + * A color is stored as a string : "#rrggbb" for a plain color + * or "#rrggbb/#rrggbb" for a two-tone color (the second color is drawn + * as stripes over the first one, like the green/yellow of the earth). + * An empty string means "no color set". + */ +namespace TerminalColor +{ + struct Entry + { + const char *english; //Source text of the translation + const char *french; //Also accepted when typed or pasted + const char *main; + const char *stripe; //nullptr if the color is plain + }; + + inline const QVector &palette() + { + static const QVector palette_ { + {"Gray", "gris", "#9e9e9e", nullptr}, + {"Blue", "bleu", "#4a90d9", nullptr}, + {"Green/Yellow", "vert/jaune", "#ffd800", "#2e9e2e"}, + {"Brown", "marron", "#8b5a2b", nullptr}, + {"Black", "noir", "#303030", nullptr}, + {"Red", "rouge", "#d32f2f", nullptr}, + {"Orange", "orange", "#f57c00", nullptr}, + {"Yellow", "jaune", "#ffd800", nullptr}, + {"Green", "vert", "#2e9e2e", nullptr}, + {"Violet", "violet", "#7b3fa0", nullptr}, + {"White", "blanc", "#ffffff", nullptr} + }; + return palette_; + } + + inline QString translatedName(const Entry &entry) { + return QCoreApplication::translate("TerminalColor", entry.english); + } + + inline QString toSpec(const QColor &main, const QColor &stripe = QColor()) + { + if (!main.isValid()) { + return QString(); + } + return stripe.isValid() ? main.name() + QLatin1Char('/') + stripe.name() + : main.name(); + } + + inline QString specOf(const Entry &entry) { + return toSpec(QColor(QLatin1String(entry.main)), + entry.stripe ? QColor(QLatin1String(entry.stripe)) : QColor()); + } + + /** + * @brief parse + * Read a color from a text : a name of the palette (in english, in french + * or translated), a color "#rrggbb" or "#rrggbb/#rrggbb". + * An empty text is valid and gives invalid colors (no color). + * @return true if the text was understood + */ + inline bool parse(const QString &text, QColor *main, QColor *stripe) + { + *main = QColor(); + *stripe = QColor(); + + const auto text_ = text.trimmed(); + if (text_.isEmpty()) { + return true; + } + + for (const auto &entry : palette()) + { + if (text_.compare(QLatin1String(entry.english), Qt::CaseInsensitive) == 0 || + text_.compare(QLatin1String(entry.french), Qt::CaseInsensitive) == 0 || + text_.compare(translatedName(entry), Qt::CaseInsensitive) == 0) + { + *main = QColor(QLatin1String(entry.main)); + if (entry.stripe) { + *stripe = QColor(QLatin1String(entry.stripe)); + } + return true; + } + } + + const auto parts_ = text_.split(QLatin1Char('/')); + if (parts_.size() == 2) + { + const QColor c1(parts_.at(0).trimmed()); + const QColor c2(parts_.at(1).trimmed()); + if (c1.isValid() && c2.isValid()) { + *main = c1; + *stripe = c2; + return true; + } + return false; + } + + const QColor color_(text_); + if (color_.isValid()) { + *main = color_; + return true; + } + return false; + } + + /** + * @return The name to display for a color : the translated name + * if the color is in the palette, else the color as text. + */ + inline QString displayName(const QColor &main, const QColor &stripe) + { + const auto spec_ = toSpec(main, stripe); + if (spec_.isEmpty()) { + return QString(); + } + for (const auto &entry : palette()) { + if (specOf(entry) == spec_) { + return translatedName(entry); + } + } + return spec_; + } + + /** + * @return A little pixmap of the color (the stripe color + * fills the upper right half for a two-tone color) + */ + inline QPixmap swatch(const QColor &main, const QColor &stripe, int size = 16) + { + QPixmap pixmap_(size, size); + pixmap_.fill(Qt::transparent); + if (!main.isValid()) { + return pixmap_; + } + + QPainter painter_(&pixmap_); + painter_.setRenderHint(QPainter::Antialiasing); + painter_.setPen(Qt::NoPen); + painter_.setBrush(main); + painter_.drawRect(0, 0, size, size); + if (stripe.isValid()) + { + painter_.setBrush(stripe); + QPolygonF polygon_; + polygon_ << QPointF(0, 0) << QPointF(size, 0) << QPointF(size, size); + painter_.drawPolygon(polygon_); + } + painter_.setPen(QPen(Qt::gray, 1)); + painter_.setBrush(Qt::NoBrush); + painter_.drawRect(0, 0, size - 1, size - 1); + return pixmap_; + } +} + +#endif // TERMINALCOLOR_H diff --git a/sources/TerminalStrip/ui/modelTerminalData.h b/sources/TerminalStrip/ui/modelTerminalData.h index 1ac7f65f2..97ad958ca 100644 --- a/sources/TerminalStrip/ui/modelTerminalData.h +++ b/sources/TerminalStrip/ui/modelTerminalData.h @@ -18,6 +18,7 @@ #ifndef MODELTERMINALDATA_H #define MODELTERMINALDATA_H +#include #include #include "../../qetgraphicsitem/element.h" #include "../realterminal.h" @@ -37,6 +38,10 @@ struct modelRealTerminalData mrtd.conductor_ = real_t->conductor(); mrtd.led_ = real_t->isLed(); mrtd.shield_ = real_t->isShield(); + mrtd.color_ = real_t->color(); + mrtd.stripe_color_ = real_t->stripeColor(); + mrtd.section_ = real_t->section(); + mrtd.width_mm_ = real_t->widthMm(); mrtd.type_ = real_t->type(); mrtd.function_ = real_t->function(); mrtd.element_ = real_t->element(); @@ -55,6 +60,10 @@ struct modelRealTerminalData QString conductor_; bool led_ = false; bool shield_ = false; + QColor color_; //Invalid = no color set by the user + QColor stripe_color_; //Second color of a two-tone color, invalid if plain + qreal section_ = 0; //In mm2, 0 = no section set by the user + qreal width_mm_ = 0; //In mm, 0 = no width set by the user bool bridged_ = false; ElementData::TerminalType type_ = ElementData::TerminalType::TTGeneric; diff --git a/sources/TerminalStrip/ui/terminalstripeditor.cpp b/sources/TerminalStrip/ui/terminalstripeditor.cpp index 478edcaea..ceafd9c33 100644 --- a/sources/TerminalStrip/ui/terminalstripeditor.cpp +++ b/sources/TerminalStrip/ui/terminalstripeditor.cpp @@ -31,6 +31,7 @@ #include "../UndoCommand/changeterminalstripcolor.h" #include "../physicalterminal.h" #include "../terminalstripbridge.h" +#include "../terminalcolor.h" #include #include @@ -217,6 +218,10 @@ void TerminalStripEditor::apply() set_info(RealTerminal::cableInfoKey(), data_.cable_); set_info(RealTerminal::cableWireInfoKey(), data_.cable_wire); set_info(RealTerminal::shieldInfoKey(), data_.shield_ ? QStringLiteral("true") : QString()); + //The color and the section are stored with the dot as decimal separator, whatever the locale + set_info(RealTerminal::colorInfoKey(), TerminalColor::toSpec(data_.color_, data_.stripe_color_)); + set_info(RealTerminal::sectionInfoKey(), data_.section_ > 0 ? QString::number(data_.section_, 'g', 6) : QString()); + set_info(RealTerminal::widthInfoKey(), data_.width_mm_ > 0 ? QString::number(data_.width_mm_, 'g', 6) : QString()); if (element->elementData() != current_data) m_project->undoStack()->push(new ChangeElementDataCommand(element, current_data)); @@ -639,14 +644,17 @@ void TerminalStripEditor::on_m_led_cb_activated(int index) namespace { /** * @return true if the cell at @a index contain a free text - * (label, cable or wire of the cable), so a text can be pasted in it. + * (label, cable, wire of the cable, color, section or width), so a text can be pasted in it. */ bool cellAcceptText(const QModelIndex &index) { const auto column_ = TerminalStripModel::columnTypeForIndex(index); return column_ == TerminalStripModel::Label || column_ == TerminalStripModel::Cable - || column_ == TerminalStripModel::CableWire; + || column_ == TerminalStripModel::CableWire + || column_ == TerminalStripModel::Color + || column_ == TerminalStripModel::Section + || column_ == TerminalStripModel::Width; } } @@ -730,7 +738,7 @@ void TerminalStripEditor::copySelectionToClipboard() * the value is set to every selected cell. * - Otherwise the text is read like a table (tabulation between the columns * and new line between the rows) and written from the top left selected cell. - * Only the label, cable and cable wire cells are modified. + * Only the label, cable, cable wire, color, section and width cells are modified. */ void TerminalStripEditor::pasteFromClipboard() { diff --git a/sources/TerminalStrip/ui/terminalstripmodel.cpp b/sources/TerminalStrip/ui/terminalstripmodel.cpp index 91bcd5d45..77ed70455 100644 --- a/sources/TerminalStrip/ui/terminalstripmodel.cpp +++ b/sources/TerminalStrip/ui/terminalstripmodel.cpp @@ -20,8 +20,12 @@ #include "../../qetgraphicsitem/element.h" #include "../physicalterminal.h" #include "../realterminal.h" +#include "../terminalcolor.h" #include "../terminalstripbridge.h" +#include +#include #include +#include #include #include #include @@ -47,10 +51,37 @@ const int TYPE_CELL = 11; const int FUNCTION_CELL = 12; const int LED_CELL = 13; const int SHIELD_CELL = 14; +const int COLOR_CELL = 15; +const int SECTION_CELL = 16; +const int WIDTH_CELL = 17; -const int COLUMN_COUNT = 15; +const int COLUMN_COUNT = 18; -static QVector UNMODIFIED_CELL_VECTOR{false, false, false, false, false, false, false, false, false, false, false, false, false, false, false}; +static QVector UNMODIFIED_CELL_VECTOR(COLUMN_COUNT, false); + +/** + * @brief parsePositiveNumber + * Convert the text written by the user to a number >= 0 + * (a section in mm2 or a width in mm). + * The comma and the dot are both accepted as decimal separator. + * @param text + * @param ok : true if the text is valid (empty text is valid and mean 0) + * @return + */ +static qreal parsePositiveNumber(const QString &text, bool *ok) +{ + auto text_ = text.trimmed(); + text_.replace(QLatin1Char(','), QLatin1Char('.')); + if (text_.isEmpty()) { + *ok = true; + return 0; + } + const auto value_ = text_.toDouble(ok); + if (*ok && value_ < 0) { + *ok = false; + } + return *ok ? value_ : 0; +} /** * @brief TerminalStripModel::levelForColumn @@ -95,6 +126,9 @@ TerminalStripModel::Column TerminalStripModel::columnTypeForIndex(const QModelIn case 12 : return Function; case 13 : return Led; case 14 : return Shield; + case 15 : return Color; + case 16 : return Section; + case 17 : return Width; default : return Invalid; } } @@ -172,6 +206,9 @@ QVariant TerminalStripModel::data(const QModelIndex &index, int role) const case CABLE_WIRE_CELL : return mrtd.cable_wire; case TYPE_CELL : return ElementData::translatedTerminalType(mrtd.type_); case FUNCTION_CELL : return ElementData::translatedTerminalFunction(mrtd.function_); + case COLOR_CELL : return TerminalColor::displayName(mrtd.color_, mrtd.stripe_color_); + case SECTION_CELL : return mrtd.section_ > 0 ? QLocale().toString(mrtd.section_, 'g', 6) : QString(); + case WIDTH_CELL : return mrtd.width_mm_ > 0 ? QLocale().toString(mrtd.width_mm_, 'g', 6) : QString(); default : return QVariant(); } } @@ -181,6 +218,9 @@ QVariant TerminalStripModel::data(const QModelIndex &index, int role) const case LABEL_CELL : return mrtd.label_; case CABLE_CELL : return mrtd.cable_; case CABLE_WIRE_CELL : return mrtd.cable_wire; + case COLOR_CELL : return TerminalColor::toSpec(mrtd.color_, mrtd.stripe_color_); + case SECTION_CELL : return mrtd.section_ > 0 ? QLocale().toString(mrtd.section_, 'g', 6) : QString(); + case WIDTH_CELL : return mrtd.width_mm_ > 0 ? QLocale().toString(mrtd.width_mm_, 'g', 6) : QString(); default: return QVariant(); } @@ -203,6 +243,13 @@ QVariant TerminalStripModel::data(const QModelIndex &index, int role) const return QBrush(Qt::yellow); } } + else if (role == Qt::DecorationRole && + index.column() == COLOR_CELL) + { + if (mrtd.color_.isValid()) { + return TerminalColor::swatch(mrtd.color_, mrtd.stripe_color_); + } + } else if (role == Qt::DecorationRole && (index.column() == LEVEL_0_CELL || index.column() == LEVEL_1_CELL || @@ -279,6 +326,46 @@ bool TerminalStripModel::setData(const QModelIndex &index, const QVariant &value modified_ = true; modified_cell = SHIELD_CELL; } + else if (column_ == COLOR_CELL && + role == Qt::EditRole) + { + //An empty text remove the color, an unknown text is ignored. + //The text can be a name of the palette (gray, blue, green/yellow...), + //or a color "#rrggbb" or "#rrggbb/#rrggbb" + QColor color_, stripe_; + if (TerminalColor::parse(value.toString(), &color_, &stripe_) && + (mrtd.color_ != color_ || mrtd.stripe_color_ != stripe_)) + { + mrtd.color_ = color_; + mrtd.stripe_color_ = stripe_; + modified_ = true; + modified_cell = COLOR_CELL; + } + } + else if (column_ == SECTION_CELL && + role == Qt::EditRole) + { + bool ok_ = false; + const auto section_ = parsePositiveNumber(value.toString(), &ok_); + if (ok_ && !qFuzzyCompare(1 + mrtd.section_, 1 + section_)) + { + mrtd.section_ = section_; + modified_ = true; + modified_cell = SECTION_CELL; + } + } + else if (column_ == WIDTH_CELL && + role == Qt::EditRole) + { + bool ok_ = false; + const auto width_ = parsePositiveNumber(value.toString(), &ok_); + if (ok_ && !qFuzzyCompare(1 + mrtd.width_mm_, 1 + width_)) + { + mrtd.width_mm_ = width_; + modified_ = true; + modified_cell = WIDTH_CELL; + } + } //Set the modification to the terminal data if (modified_) @@ -326,6 +413,9 @@ QVariant TerminalStripModel::headerData(int section, Qt::Orientation orientation case FUNCTION_CELL : return tr("Function"); case LED_CELL: return tr("led"); case SHIELD_CELL: return tr("Shield"); + case COLOR_CELL: return tr("Color"); + case SECTION_CELL: return tr("Section (mm²)"); + case WIDTH_CELL: return tr("Width (mm)"); default : return QVariant(); } } @@ -340,7 +430,8 @@ Qt::ItemFlags TerminalStripModel::flags(const QModelIndex &index) const auto c = index.column(); if (c == LABEL_CELL || c == TYPE_CELL || c == FUNCTION_CELL - || c == CABLE_CELL || c == CABLE_WIRE_CELL) + || c == CABLE_CELL || c == CABLE_WIRE_CELL + || c == COLOR_CELL || c == SECTION_CELL || c == WIDTH_CELL) flags = flags | Qt::ItemIsEditable; if (c == LED_CELL || c == SHIELD_CELL) { flags = flags | Qt::ItemIsUserCheckable; @@ -819,6 +910,40 @@ QPixmap TerminalStripModel::bridgePixmapFor(const QModelIndex &index) const * and a spinbox to edit the level of a terminal **********************************************************/ +namespace { +/** + * Widths (mm) of terminals sold by the manufacturers. + * Only the values found in the catalogs of the manufacturers are listed, + * any other width can be typed in the cell. + * For ABB Entrelec the value is the spacing (pitch) given by the catalog. + */ +struct WidthEntry { double mm; const char *label; }; +const WidthEntry WIDTH_ENTRIES[] { + {5.1, "Weidmüller WDU 2.5"}, + {5.2, "WAGO 2002 (TOPJOB S)"}, + {6.0, "ABB Entrelec M 4/6"}, + {6.1, "Weidmüller WDU 4"}, + {6.2, "Phoenix Contact UT 4"}, + {8.0, "ABB Entrelec M 6/8, M 4/8"}, + {8.2, "Phoenix Contact UT 6"}, + {10.0, "ABB Entrelec M 10/10"}, + {10.2, "Phoenix Contact UT 10"}, + {11.9, "Weidmüller WDU 16/ZA"}, + {12.2, "Phoenix Contact UT 16"}, + {16.0, "Phoenix Contact UT 35, Weidmüller WDU 35N"}, + {26.0, "ABB Entrelec M 35/26.FF"}, + {31.0, "ABB Entrelec M 70/31.AF"}, + {35.0, "ABB Entrelec M 120/35.F"}, + {42.0, "ABB Entrelec M 300/42.FF"} +}; + +QString widthItemText(double mm, const QString &label = QString()) +{ + const auto text_ = QCoreApplication::translate("TerminalStripModelDelegate", "%1 mm").arg(QLocale().toString(mm, 'g', 6)); + return label.isEmpty() ? text_ : text_ + QStringLiteral(" - ") + label; +} +} + TerminalStripModelDelegate::TerminalStripModelDelegate(QObject *parent) : QStyledItemDelegate(parent) {} @@ -850,10 +975,125 @@ QWidget *TerminalStripModelDelegate::createEditor(QWidget *parent, const QStyleO return qcb; } + if (index.column() == SECTION_CELL) { + auto qcb = new QComboBox(parent); + qcb->setObjectName(QStringLiteral("terminal_section")); + qcb->addItem(tr("None (default)"), 0.0); + //Usual sections, in mm2 + static const QVector sections_ {0.5, 0.75, 1, 1.5, 2.5, 4, 6, 10, 16, 25, 35, 50, 70, 95, 120, 150, 185, 240}; + for (const auto section_ : sections_) { + qcb->addItem(tr("%1 mm²").arg(QLocale().toString(section_, 'g', 6)), section_); + } + + return qcb; + } + if (index.column() == WIDTH_CELL) { + auto qcb = new QComboBox(parent); + qcb->setObjectName(QStringLiteral("terminal_width")); + qcb->addItem(tr("None (default)"), 0.0); + for (const auto &entry : WIDTH_ENTRIES) { + qcb->addItem(widthItemText(entry.mm, QString::fromUtf8(entry.label)), entry.mm); + } + + return qcb; + } + if (index.column() == COLOR_CELL) { + auto qcb = new QComboBox(parent); + qcb->setObjectName(QStringLiteral("terminal_color")); + qcb->addItem(tr("None (default)"), QString()); + for (const auto &entry : TerminalColor::palette()) + { + const auto spec_ = TerminalColor::specOf(entry); + QColor main_, stripe_; + TerminalColor::parse(spec_, &main_, &stripe_); + qcb->addItem(QIcon(TerminalColor::swatch(main_, stripe_)), + TerminalColor::translatedName(entry), + spec_); + } + + return qcb; + } return QStyledItemDelegate::createEditor(parent, option, index); } +void TerminalStripModelDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const +{ + if (index.isValid() && + editor->objectName() == QLatin1String("terminal_color")) + { + if (auto qcb = dynamic_cast(editor)) + { + const auto spec_ = index.data(Qt::EditRole).toString(); + auto pos_ = qcb->findData(spec_); + if (pos_ < 0) + { + //The color is not in the palette (typed or pasted), keep it in the list + QColor main_, stripe_; + TerminalColor::parse(spec_, &main_, &stripe_); + qcb->addItem(QIcon(TerminalColor::swatch(main_, stripe_)), spec_, spec_); + pos_ = qcb->count() - 1; + } + qcb->setCurrentIndex(pos_); + } + return; + } + + if (index.isValid() && + editor->objectName() == QLatin1String("terminal_section")) + { + if (auto qcb = dynamic_cast(editor)) + { + bool ok_ = false; + const auto section_ = parsePositiveNumber(index.data(Qt::EditRole).toString(), &ok_); + auto pos_ = -1; + for (auto i = 0 ; i < qcb->count() ; ++i) + { + if (qFuzzyCompare(1 + qcb->itemData(i).toDouble(), 1 + section_)) { + pos_ = i; + break; + } + } + if (pos_ < 0) + { + //The section is not in the list (typed or pasted), keep it in the list + qcb->addItem(tr("%1 mm²").arg(QLocale().toString(section_, 'g', 6)), section_); + pos_ = qcb->count() - 1; + } + qcb->setCurrentIndex(pos_); + } + return; + } + + if (index.isValid() && + editor->objectName() == QLatin1String("terminal_width")) + { + if (auto qcb = dynamic_cast(editor)) + { + bool ok_ = false; + const auto width_ = parsePositiveNumber(index.data(Qt::EditRole).toString(), &ok_); + auto pos_ = -1; + for (auto i = 0 ; i < qcb->count() ; ++i) + { + if (qFuzzyCompare(1 + qcb->itemData(i).toDouble(), 1 + width_)) { + pos_ = i; + break; + } + } + if (pos_ < 0) + { + //The width is not in the list (typed or pasted), keep it in the list + qcb->addItem(widthItemText(width_), width_); + pos_ = qcb->count() - 1; + } + qcb->setCurrentIndex(pos_); + } + return; + } + + QStyledItemDelegate::setEditorData(editor, index); +} + void TerminalStripModelDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const { if (index.isValid()) @@ -870,6 +1110,24 @@ void TerminalStripModelDelegate::setModelData(QWidget *editor, QAbstractItemMode model->setData(index, qcb->currentData(), Qt::EditRole); } } + else if (editor->objectName() == QLatin1String("terminal_section")) + { + if (auto qcb = dynamic_cast(editor)) { + model->setData(index, qcb->currentData().toDouble(), Qt::EditRole); + } + } + else if (editor->objectName() == QLatin1String("terminal_width")) + { + if (auto qcb = dynamic_cast(editor)) { + model->setData(index, qcb->currentData().toDouble(), Qt::EditRole); + } + } + else if (editor->objectName() == QLatin1String("terminal_color")) + { + if (auto qcb = dynamic_cast(editor)) { + model->setData(index, qcb->currentData(), Qt::EditRole); + } + } else { QStyledItemDelegate::setModelData(editor, model, index); } diff --git a/sources/TerminalStrip/ui/terminalstripmodel.h b/sources/TerminalStrip/ui/terminalstripmodel.h index 2de3bec73..1a1f89fc5 100644 --- a/sources/TerminalStrip/ui/terminalstripmodel.h +++ b/sources/TerminalStrip/ui/terminalstripmodel.h @@ -62,6 +62,9 @@ class TerminalStripModel : public QAbstractTableModel Function = 12, Led = 13, Shield = 14, + Color = 15, + Section = 16, + Width = 17, Invalid = 99 }; @@ -124,6 +127,9 @@ class TerminalStripModelDelegate : public QStyledItemDelegate QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override; + void setEditorData( + QWidget *editor, + const QModelIndex &index) const override; void setModelData( QWidget *editor, QAbstractItemModel *model,