Improve terminal_strip

This commit is contained in:
Laurent Trinques
2026-10-15 14:38:11 +02:00
parent 892a899a20
commit e883d2c3bf
11 changed files with 708 additions and 9 deletions
@@ -18,6 +18,7 @@
#include "terminalstripdrawer.h" #include "terminalstripdrawer.h"
#include <QPainter> #include <QPainter>
#include <QPolygonF>
#include <QFontMetricsF> #include <QFontMetricsF>
#include <QHash> #include <QHash>
@@ -405,6 +406,105 @@ void drawCables(QPainter *painter, const QVector<CableCell> &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<AbstractRealTerminalInterface> &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<AbstractRealTerminalInterface> &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<qreal>(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 } //End anonymous namespace
/** /**
@@ -509,7 +609,7 @@ void TerminalStripDrawer::paint(QPainter *painter)
break; break;
} }
terminal_rect = m_pattern->m_terminal_rect[index_]; terminal_rect = terminalRect(*m_pattern, index_, real_terminal_vector[i]);
//Cable (hose) of this terminal //Cable (hose) of this terminal
CableCell cable_cell; 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()}; const bool have_cable{m_pattern->m_cable_wire_length > 0 && !cable_cell.cable.isEmpty()};
cable_cells.append(cable_cell); 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); 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...) //Draw the symbol of the terminal type (fuse, ground, led...)
if (m_pattern->m_type_symbol_height > 0 && real_terminal_vector[i]) if (m_pattern->m_type_symbol_height > 0 && real_terminal_vector[i])
{ {
painter->save();
if (fill_color_.isValid()) {
painter->setPen(text_color_);
}
drawTypeSymbol(painter, drawTypeSymbol(painter,
QRectF{0, m_pattern->m_type_symbol_y, QRectF{0, m_pattern->m_type_symbol_y,
terminal_rect.width(), m_pattern->m_type_symbol_height}, terminal_rect.width(), m_pattern->m_type_symbol_height},
real_terminal_vector[i]->type(), real_terminal_vector[i]->type(),
real_terminal_vector[i]->isLed()); real_terminal_vector[i]->isLed());
painter->restore();
} }
//Draw the connections, above and under the terminal //Draw the connections, above and under the terminal
@@ -570,6 +689,9 @@ void TerminalStripDrawer::paint(QPainter *painter)
//Draw text //Draw text
painter->save(); painter->save();
if (fill_color_.isValid()) {
painter->setPen(text_color_);
}
text_rect.setRect(0, terminals_text_y, terminal_rect.width(), terminals_text_height); text_rect.setRect(0, terminals_text_y, terminal_rect.width(), terminals_text_height);
if (terminals_text_orientation == Qt::Vertical) if (terminals_text_orientation == Qt::Vertical)
{ {
@@ -594,6 +716,9 @@ void TerminalStripDrawer::paint(QPainter *painter)
//Draw xref //Draw xref
xref_rect.setRect(0, xref_text_y, terminal_rect.width(), xref_text_height); xref_rect.setRect(0, xref_text_y, terminal_rect.width(), xref_text_height);
painter->save(); painter->save();
if (fill_color_.isValid()) {
painter->setPen(text_color_);
}
if (xref_text_orientation == Qt::Vertical) if (xref_text_orientation == Qt::Vertical)
{ {
painter->translate(xref_rect.bottomLeft()); painter->translate(xref_rect.bottomLeft());
@@ -848,7 +973,7 @@ qreal TerminalStripDrawer::width() const
break; break;
} }
width_ += m_pattern->m_terminal_rect[index_].width(); width_ += terminalRect(*m_pattern, index_, real_terminal_vector[i]).width();
} }
} }
} }
@@ -18,6 +18,7 @@
#ifndef TERMINALSTRIPDRAWER_H #ifndef TERMINALSTRIPDRAWER_H
#define TERMINALSTRIPDRAWER_H #define TERMINALSTRIPDRAWER_H
#include <QColor>
#include <QPointer> #include <QPointer>
#include <QUuid> #include <QUuid>
@@ -77,6 +78,13 @@ namespace TerminalStripDrawer
virtual QString cable() const { return QString(); } virtual QString cable() const { return QString(); }
virtual QString cableWire() const { return QString(); } virtual QString cableWire() const { return QString(); }
virtual bool isShield() const { return false; } 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 class AbstractPhysicalTerminalInterface
@@ -153,6 +153,21 @@ namespace TerminalStripDrawer
return m_real ? m_real->isShield() : false; 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<TerminalStripBridge> bridge) : TrueBridge::TrueBridge(QSharedPointer<TerminalStripBridge> bridge) :
m_bridge { bridge } m_bridge { bridge }
{} {}
@@ -64,6 +64,9 @@ namespace TerminalStripDrawer
QString cable() const override; QString cable() const override;
QString cableWire() const override; QString cableWire() const override;
bool isShield() const override; bool isShield() const override;
QColor color() const override;
QColor stripeColor() const override;
qreal widthMm() const override;
private: private:
QSharedPointer<RealTerminal> m_real; QSharedPointer<RealTerminal> m_real;
+73
View File
@@ -19,6 +19,7 @@
#include "terminalstrip.h" #include "terminalstrip.h"
#include "../qetgraphicsitem/terminalelement.h" #include "../qetgraphicsitem/terminalelement.h"
#include "physicalterminal.h" #include "physicalterminal.h"
#include "terminalcolor.h"
#include "../qetgraphicsitem/conductor.h" #include "../qetgraphicsitem/conductor.h"
/** /**
@@ -191,6 +192,78 @@ bool RealTerminal::isShield() const {
return false; 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 * @brief RealTerminal::conductor
* @return * @return
+10
View File
@@ -19,6 +19,7 @@
#define REALTERMINAL_H #define REALTERMINAL_H
#include <QSharedPointer> #include <QSharedPointer>
#include <QColor>
#include <QDomElement> #include <QDomElement>
#include "../properties/elementdata.h" #include "../properties/elementdata.h"
@@ -66,11 +67,20 @@ class RealTerminal
QString cable() const; QString cable() const;
QString cableWire() const; QString cableWire() const;
bool isShield() 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 //Keys used to store the cable data in the element informations
static QString cableInfoKey() { return QStringLiteral("terminal_cable"); } static QString cableInfoKey() { return QStringLiteral("terminal_cable"); }
static QString cableWireInfoKey() { return QStringLiteral("terminal_cable_wire"); } static QString cableWireInfoKey() { return QStringLiteral("terminal_cable_wire"); }
static QString shieldInfoKey() { return QStringLiteral("terminal_shield"); } 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; QString conductor() const;
+184
View File
@@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef TERMINALCOLOR_H
#define TERMINALCOLOR_H
#include <QColor>
#include <QCoreApplication>
#include <QPainter>
#include <QPixmap>
#include <QPolygonF>
#include <QString>
#include <QVector>
/**
* 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<Entry> &palette()
{
static const QVector<Entry> 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
@@ -18,6 +18,7 @@
#ifndef MODELTERMINALDATA_H #ifndef MODELTERMINALDATA_H
#define MODELTERMINALDATA_H #define MODELTERMINALDATA_H
#include <QColor>
#include <QString> #include <QString>
#include "../../qetgraphicsitem/element.h" #include "../../qetgraphicsitem/element.h"
#include "../realterminal.h" #include "../realterminal.h"
@@ -37,6 +38,10 @@ struct modelRealTerminalData
mrtd.conductor_ = real_t->conductor(); mrtd.conductor_ = real_t->conductor();
mrtd.led_ = real_t->isLed(); mrtd.led_ = real_t->isLed();
mrtd.shield_ = real_t->isShield(); 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.type_ = real_t->type();
mrtd.function_ = real_t->function(); mrtd.function_ = real_t->function();
mrtd.element_ = real_t->element(); mrtd.element_ = real_t->element();
@@ -55,6 +60,10 @@ struct modelRealTerminalData
QString conductor_; QString conductor_;
bool led_ = false; bool led_ = false;
bool shield_ = 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; bool bridged_ = false;
ElementData::TerminalType type_ = ElementData::TerminalType::TTGeneric; ElementData::TerminalType type_ = ElementData::TerminalType::TTGeneric;
@@ -31,6 +31,7 @@
#include "../UndoCommand/changeterminalstripcolor.h" #include "../UndoCommand/changeterminalstripcolor.h"
#include "../physicalterminal.h" #include "../physicalterminal.h"
#include "../terminalstripbridge.h" #include "../terminalstripbridge.h"
#include "../terminalcolor.h"
#include <QApplication> #include <QApplication>
#include <QClipboard> #include <QClipboard>
@@ -217,6 +218,10 @@ void TerminalStripEditor::apply()
set_info(RealTerminal::cableInfoKey(), data_.cable_); set_info(RealTerminal::cableInfoKey(), data_.cable_);
set_info(RealTerminal::cableWireInfoKey(), data_.cable_wire); set_info(RealTerminal::cableWireInfoKey(), data_.cable_wire);
set_info(RealTerminal::shieldInfoKey(), data_.shield_ ? QStringLiteral("true") : QString()); 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) if (element->elementData() != current_data)
m_project->undoStack()->push(new ChangeElementDataCommand(element, 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 { namespace {
/** /**
* @return true if the cell at @a index contain a free text * @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) bool cellAcceptText(const QModelIndex &index)
{ {
const auto column_ = TerminalStripModel::columnTypeForIndex(index); const auto column_ = TerminalStripModel::columnTypeForIndex(index);
return column_ == TerminalStripModel::Label return column_ == TerminalStripModel::Label
|| column_ == TerminalStripModel::Cable || 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. * the value is set to every selected cell.
* - Otherwise the text is read like a table (tabulation between the columns * - 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. * 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() void TerminalStripEditor::pasteFromClipboard()
{ {
+261 -3
View File
@@ -20,8 +20,12 @@
#include "../../qetgraphicsitem/element.h" #include "../../qetgraphicsitem/element.h"
#include "../physicalterminal.h" #include "../physicalterminal.h"
#include "../realterminal.h" #include "../realterminal.h"
#include "../terminalcolor.h"
#include "../terminalstripbridge.h" #include "../terminalstripbridge.h"
#include <QColor>
#include <QCoreApplication>
#include <QDebug> #include <QDebug>
#include <QLocale>
#include <QBrush> #include <QBrush>
#include <QVector> #include <QVector>
#include <QComboBox> #include <QComboBox>
@@ -47,10 +51,37 @@ const int TYPE_CELL = 11;
const int FUNCTION_CELL = 12; const int FUNCTION_CELL = 12;
const int LED_CELL = 13; const int LED_CELL = 13;
const int SHIELD_CELL = 14; 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<bool> UNMODIFIED_CELL_VECTOR{false, false, false, false, false, false, false, false, false, false, false, false, false, false, false}; static QVector<bool> 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 * @brief TerminalStripModel::levelForColumn
@@ -95,6 +126,9 @@ TerminalStripModel::Column TerminalStripModel::columnTypeForIndex(const QModelIn
case 12 : return Function; case 12 : return Function;
case 13 : return Led; case 13 : return Led;
case 14 : return Shield; case 14 : return Shield;
case 15 : return Color;
case 16 : return Section;
case 17 : return Width;
default : return Invalid; 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 CABLE_WIRE_CELL : return mrtd.cable_wire;
case TYPE_CELL : return ElementData::translatedTerminalType(mrtd.type_); case TYPE_CELL : return ElementData::translatedTerminalType(mrtd.type_);
case FUNCTION_CELL : return ElementData::translatedTerminalFunction(mrtd.function_); 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(); default : return QVariant();
} }
} }
@@ -181,6 +218,9 @@ QVariant TerminalStripModel::data(const QModelIndex &index, int role) const
case LABEL_CELL : return mrtd.label_; case LABEL_CELL : return mrtd.label_;
case CABLE_CELL : return mrtd.cable_; case CABLE_CELL : return mrtd.cable_;
case CABLE_WIRE_CELL : return mrtd.cable_wire; 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(); default: return QVariant();
} }
@@ -203,6 +243,13 @@ QVariant TerminalStripModel::data(const QModelIndex &index, int role) const
return QBrush(Qt::yellow); 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 && else if (role == Qt::DecorationRole &&
(index.column() == LEVEL_0_CELL || (index.column() == LEVEL_0_CELL ||
index.column() == LEVEL_1_CELL || index.column() == LEVEL_1_CELL ||
@@ -279,6 +326,46 @@ bool TerminalStripModel::setData(const QModelIndex &index, const QVariant &value
modified_ = true; modified_ = true;
modified_cell = SHIELD_CELL; 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 //Set the modification to the terminal data
if (modified_) if (modified_)
@@ -326,6 +413,9 @@ QVariant TerminalStripModel::headerData(int section, Qt::Orientation orientation
case FUNCTION_CELL : return tr("Function"); case FUNCTION_CELL : return tr("Function");
case LED_CELL: return tr("led"); case LED_CELL: return tr("led");
case SHIELD_CELL: return tr("Shield"); 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(); default : return QVariant();
} }
} }
@@ -340,7 +430,8 @@ Qt::ItemFlags TerminalStripModel::flags(const QModelIndex &index) const
auto c = index.column(); auto c = index.column();
if (c == LABEL_CELL || c == TYPE_CELL || c == FUNCTION_CELL 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; flags = flags | Qt::ItemIsEditable;
if (c == LED_CELL || c == SHIELD_CELL) { if (c == LED_CELL || c == SHIELD_CELL) {
flags = flags | Qt::ItemIsUserCheckable; 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 * 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) : TerminalStripModelDelegate::TerminalStripModelDelegate(QObject *parent) :
QStyledItemDelegate(parent) QStyledItemDelegate(parent)
{} {}
@@ -850,10 +975,125 @@ QWidget *TerminalStripModelDelegate::createEditor(QWidget *parent, const QStyleO
return qcb; 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<double> 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); 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<QComboBox *>(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<QComboBox *>(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<QComboBox *>(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 void TerminalStripModelDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
{ {
if (index.isValid()) if (index.isValid())
@@ -870,6 +1110,24 @@ void TerminalStripModelDelegate::setModelData(QWidget *editor, QAbstractItemMode
model->setData(index, qcb->currentData(), Qt::EditRole); model->setData(index, qcb->currentData(), Qt::EditRole);
} }
} }
else if (editor->objectName() == QLatin1String("terminal_section"))
{
if (auto qcb = dynamic_cast<QComboBox *>(editor)) {
model->setData(index, qcb->currentData().toDouble(), Qt::EditRole);
}
}
else if (editor->objectName() == QLatin1String("terminal_width"))
{
if (auto qcb = dynamic_cast<QComboBox *>(editor)) {
model->setData(index, qcb->currentData().toDouble(), Qt::EditRole);
}
}
else if (editor->objectName() == QLatin1String("terminal_color"))
{
if (auto qcb = dynamic_cast<QComboBox *>(editor)) {
model->setData(index, qcb->currentData(), Qt::EditRole);
}
}
else { else {
QStyledItemDelegate::setModelData(editor, model, index); QStyledItemDelegate::setModelData(editor, model, index);
} }
@@ -62,6 +62,9 @@ class TerminalStripModel : public QAbstractTableModel
Function = 12, Function = 12,
Led = 13, Led = 13,
Shield = 14, Shield = 14,
Color = 15,
Section = 16,
Width = 17,
Invalid = 99 Invalid = 99
}; };
@@ -124,6 +127,9 @@ class TerminalStripModelDelegate : public QStyledItemDelegate
QWidget *parent, QWidget *parent,
const QStyleOptionViewItem &option, const QStyleOptionViewItem &option,
const QModelIndex &index) const override; const QModelIndex &index) const override;
void setEditorData(
QWidget *editor,
const QModelIndex &index) const override;
void setModelData( void setModelData(
QWidget *editor, QWidget *editor,
QAbstractItemModel *model, QAbstractItemModel *model,