/* Copyright 2006-2021 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 . */ #include "terminalstripmodel.h" #include "../terminalstrip.h" #include "../../qetgraphicsitem/element.h" #include "../physicalterminal.h" #include "../realterminal.h" #include "../terminalstripbridge.h" #include #include #include #include #include #include #include /** * Some const int who describe what a column contain */ const int POS_CELL = 0; const int LEVEL_CELL = 1; const int LEVEL_0_CELL = 2; const int LEVEL_1_CELL = 3; const int LEVEL_2_CELL = 4; const int LEVEL_3_CELL = 5; const int LABEL_CELL = 6; const int XREF_CELL = 7; const int CABLE_CELL = 8; const int CABLE_WIRE_CELL = 9; const int TYPE_CELL = 10; const int FUNCTION_CELL = 11; const int LED_CELL = 12; const int CONDUCTOR_CELL = 13; const int ROW_COUNT = 13; static QVector UNMODIFIED_CELL_VECTOR{false, false, false, false, false, false, false, false, false, false, false, false, false, false}; /** * @brief TerminalStripModel::levelForColumn * Return the terminal level for column @a column * or -1 if column is not a level column * @param column * @return */ int TerminalStripModel::levelForColumn(Column column) { switch (column) { case Level0: return 0; case Level1: return 1; case Level2: return 2; case Level3: return 3; default: return -1; } } /** * @brief TerminalStripModel::columnTypeForIndex * @param index * @return the thing (pos, level, type, function etc...) for @a index */ TerminalStripModel::Column TerminalStripModel::columnTypeForIndex(const QModelIndex &index) { if (index.isValid()) { switch (index.column()) { case 0: return Pos; case 1: return Level; case 2 : return Level0; case 3 : return Level1; case 4 : return Level2; case 5 : return Level3; case 6 : return Label; case 7 : return XRef; case 8 : return Cable; case 9 : return CableWire; case 10 : return Type; case 11 : return Function; case 12 : return Led; case 13 : return Conductor; default : return Invalid; } } return Invalid; } /** * @brief TerminalStripModel::TerminalStripModel * @param terminal_strip * @param parent */ TerminalStripModel::TerminalStripModel(TerminalStrip *terminal_strip, QObject *parent) : QAbstractTableModel(parent), m_terminal_strip(terminal_strip) { fillPhysicalTerminalData(); connect(terminal_strip, &TerminalStrip::bridgeColorChanged, this, [=] { emit dataChanged(index(0, LEVEL_0_CELL), index(rowCount(), LEVEL_3_CELL)); }); } /** * @brief TerminalStripModel::setTerminalStrip * set the current terminal strip of this model to @a terminal_strip. * @param terminal_strip */ void TerminalStripModel::setTerminalStrip(TerminalStrip *terminal_strip) { m_terminal_strip = terminal_strip; reload(); } int TerminalStripModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent) if (!m_terminal_strip) { return 0; } auto count = 0; for (const auto &mptd : m_physical_data) { count += mptd.real_data.size(); } return count; } int TerminalStripModel::columnCount(const QModelIndex &parent) const { Q_UNUSED(parent) return ROW_COUNT; } QVariant TerminalStripModel::data(const QModelIndex &index, int role) const { if (index.row() >= rowCount(QModelIndex())) { return QVariant(); } const auto mrtd = dataAtRow(index.row()); if (role == Qt::DisplayRole) { switch (index.column()) { case POS_CELL : return physicalDataAtIndex(index.row()).pos_; case LEVEL_CELL : return mrtd.level_; case LABEL_CELL : return mrtd.label_; case XREF_CELL : return mrtd.Xref_; case CABLE_CELL : return mrtd.cable_; 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 CONDUCTOR_CELL : return mrtd.conductor_; default : return QVariant(); } } else if (role == Qt::EditRole) { switch (index.column()) { case LABEL_CELL : return mrtd.label_; default: return QVariant(); } } else if (role == Qt::CheckStateRole && index.column() == LED_CELL) { return mrtd.led_ ? Qt::Checked : Qt::Unchecked; } else if (role == Qt::BackgroundRole && index.column() <= CONDUCTOR_CELL ) { if (m_modified_cell.contains(mrtd.element_) && m_modified_cell.value(mrtd.element_).at(index.column())) { return QBrush(Qt::yellow); } } else if (role == Qt::DecorationRole && (index.column() == LEVEL_0_CELL || index.column() == LEVEL_1_CELL || index.column() == LEVEL_2_CELL || index.column() == LEVEL_3_CELL)) { return bridgePixmapFor(index); } return QVariant(); } bool TerminalStripModel::setData(const QModelIndex &index, const QVariant &value, int role) { auto mrtd = dataAtRow(index.row()); bool modified_ = false; int modified_cell = -1; auto column_ = index.column(); if (column_ == LEVEL_CELL && role == Qt::EditRole) { mrtd.level_ = value.toInt(); modified_ = true; modified_cell = LEVEL_CELL; } else if (column_ == LED_CELL) { mrtd.led_ = value.toBool(); modified_ = true; modified_cell = LED_CELL; } else if (column_ == TYPE_CELL && role == Qt::EditRole) { mrtd.type_ = value.value(); modified_ = true; modified_cell = TYPE_CELL; } else if (column_ == FUNCTION_CELL && role == Qt::EditRole) { mrtd.function_ = value.value(); modified_ = true; modified_cell = FUNCTION_CELL; } else if (column_ == LABEL_CELL && role == Qt::EditRole && mrtd.label_ != value.toString()) { mrtd.label_ = value.toString(); modified_ = true; modified_cell = LABEL_CELL; } //Set the modification to the terminal data if (modified_) { replaceDataAtRow(mrtd, index.row()); if (mrtd.element_) { QVector vector_; if (m_modified_cell.contains(mrtd.element_)) { vector_ = m_modified_cell.value(mrtd.element_); } else { vector_ = UNMODIFIED_CELL_VECTOR; } vector_.replace(modified_cell, true); m_modified_cell.insert(mrtd.element_, vector_); } emit dataChanged(index, index); return true; } return false; } QVariant TerminalStripModel::headerData(int section, Qt::Orientation orientation, int role) const { if (role == Qt::DisplayRole) { if (orientation == Qt::Horizontal) { switch (section) { case POS_CELL: return tr("Position"); case LEVEL_CELL: return tr("Étage"); case LEVEL_0_CELL: return QStringLiteral("0"); case LEVEL_1_CELL: return QStringLiteral("1"); case LEVEL_2_CELL: return QStringLiteral("2"); case LEVEL_3_CELL: return QStringLiteral("3"); case LABEL_CELL: return tr("Label"); case XREF_CELL: return tr("Référence croisé"); case CABLE_CELL: return tr("Câble"); case CABLE_WIRE_CELL: return tr("Couleur / numéro de fil câble"); case TYPE_CELL: return tr("Type"); case FUNCTION_CELL : return tr("Fonction"); case LED_CELL: return tr("led"); case CONDUCTOR_CELL: return tr("Numéro de conducteur"); default : return QVariant(); } } } return QVariant(); } Qt::ItemFlags TerminalStripModel::flags(const QModelIndex &index) const { Qt::ItemFlags flags = Qt::ItemIsEnabled | Qt::ItemIsSelectable; auto c = index.column(); if (c == LABEL_CELL || c == TYPE_CELL || c == FUNCTION_CELL) flags = flags | Qt::ItemIsEditable; if (c == LED_CELL) { flags = flags | Qt::ItemIsUserCheckable; } return flags; } /** * @brief TerminalStripModel::modifiedRealTerminalData * @return a vector of modified terminal. */ QVector TerminalStripModel::modifiedmodelRealTerminalData() const { QVector returned_vector; const auto modified_real_terminal = m_modified_cell.keys(); for (auto i = 0 ; i TerminalStripModel::modelPhysicalTerminalDataForIndex(QModelIndexList index_list) const { QVector vector_; if (index_list.isEmpty()) { return vector_; } QSet set_; //We use a QSet to avoid insert several time the same terminal. for (auto index : index_list) { if (index.isValid()) { set_.insert(index.row()); } } for (const auto i : set_) { const auto phy = physicalDataAtIndex(i); if (!vector_.contains(phy)) { vector_.append(phy); } } return vector_; } /** * @brief TerminalStripModel::realTerminalDataForIndex * @param index_list * @return */ QVector TerminalStripModel::modelRealTerminalDataForIndex(QModelIndexList index_list) const { QVector vector_; if (index_list.isEmpty()) { return vector_; } QSet set_; //We use a QSet to avoid insert several time the same terminal. for (auto index : index_list) { if (index.isValid()) { set_.insert(index.row()); } } for (auto i : set_) { const auto rtd_ = realDataAtIndex(i); if (rtd_.level_ > -1) { vector_.append(realDataAtIndex(i)); } } return vector_; } /** * @brief TerminalStripModel::realTerminalDataForIndex * @param index * @return modelRealTerminalData at index @a index or null modelRealTerminalData if invalid */ modelRealTerminalData TerminalStripModel::modelRealTerminalDataForIndex(const QModelIndex &index) const { if (index.isValid()) { return realDataAtIndex(index.row()); } else { return modelRealTerminalData(); } } /** * @brief TerminalStripModel::buildBridgePixmap * Build the pixmap of bridge. * You should call this method when you know the * size to use in the bridge cell of a QTableView. * @param pixmap_size */ void TerminalStripModel::buildBridgePixmap(const QSize &pixmap_size) { m_bridges_pixmaps.clear(); for (auto color_ : TerminalStripBridge::bridgeColor()) { QPen pen; pen.setColor(color_); pen.setWidth(1); QBrush brush; brush.setColor(color_); brush.setStyle(Qt::SolidPattern); QPixmap top_(pixmap_size); top_.fill(Qt::lightGray); QPainter top_p(&top_); top_p.setPen(pen); top_p.setBrush(brush); QPixmap middle_(pixmap_size); middle_.fill(Qt::lightGray); QPainter middle_p(&middle_); middle_p.setPen(pen); middle_p.setBrush(brush); QPixmap bottom_(pixmap_size); bottom_.fill(Qt::lightGray); QPainter bottom_p(&bottom_); bottom_p.setPen(pen); bottom_p.setBrush(brush); QPixmap none_(pixmap_size); none_.fill(Qt::lightGray); QPainter none_p(&none_); none_p.setPen(pen); none_p.setBrush(brush); auto w_ = pixmap_size.width(); auto h_ = pixmap_size.height(); //Draw circle top_p.drawEllipse(QPoint(w_/2, h_/2), w_/4, h_/4); middle_p.drawEllipse(QPoint(w_/2, h_/2), w_/4, h_/4); bottom_p.drawEllipse(QPoint(w_/2, h_/2), w_/4, h_/4); //Draw top line middle_p.drawRect((w_/2)-(w_/8), 0, w_/4, h_/2); bottom_p.drawRect((w_/2)-(w_/8), 0, w_/4, h_/2); none_p.drawRect((w_/2)-(w_/8), 0, w_/4, h_/2); //Draw bottom line top_p.drawRect((w_/2)-(w_/8), h_/2, w_/4, h_/2); middle_p.drawRect((w_/2)-(w_/8), h_/2, w_/4, h_/2); none_p.drawRect((w_/2)-(w_/8), h_/2, w_/4, h_/2); BridgePixmap bpxm; bpxm.top_ = top_; bpxm.middle_ = middle_; bpxm.bottom_ = bottom_; bpxm.none_ = none_; m_bridges_pixmaps.insert(color_, bpxm); } } /** * @brief TerminalStripModel::reload * Reload (reset) the model */ void TerminalStripModel::reload() { beginResetModel(); m_physical_data.clear(); m_modified_cell.clear(); fillPhysicalTerminalData(); endResetModel(); } void TerminalStripModel::fillPhysicalTerminalData() { //Get all physical terminal if (m_terminal_strip) { for (const auto &phy_t : m_terminal_strip->physicalTerminal()) { modelPhysicalTerminalData mptd; mptd.pos_ = phy_t->pos(); mptd.uuid_ = phy_t->uuid(); for (const auto &real_t : phy_t->realTerminals()) { if (!real_t.isNull()) { mptd.real_data.append(modelRealTerminalData::data(real_t)); } } m_physical_data.append(mptd); } } } modelRealTerminalData TerminalStripModel::dataAtRow(int row) const { if (row > rowCount(QModelIndex())) { return modelRealTerminalData(); } else { auto current_row = 0; for (const auto &physical_data : qAsConst(m_physical_data)) { for (const auto &real_data : physical_data.real_data) { if (current_row == row) { return real_data; } else { ++current_row; } } } } return modelRealTerminalData(); } /** * @brief TerminalStripModel::replaceDataAtRow * Replace the data at row \p row by \p data * @param data * @param row */ void TerminalStripModel::replaceDataAtRow(modelRealTerminalData data, int row) { if (row > rowCount(QModelIndex())) { return; } else { auto current_row = 0; auto current_physical = 0; for (const auto &physical_data : qAsConst(m_physical_data)) { auto current_real = 0; for (int i=0 ; i= index) { match_ = true; break; } } if (match_) { return m_physical_data.at(current_phy); } else { return modelPhysicalTerminalData(); } } /** * @brief TerminalStripModel::realDataAtIndex * @param index * @return the realTerminalData at index \p index. */ modelRealTerminalData TerminalStripModel::realDataAtIndex(int index) const { if (m_physical_data.isEmpty()) { return modelRealTerminalData(); } int current_checked_index = -1; for (const auto & ptd_ : qAsConst(m_physical_data)) { for (const auto & rtd_ : qAsConst(ptd_.real_data)) { ++current_checked_index; if (current_checked_index == index) { return rtd_; } } } return modelRealTerminalData(); } QPixmap TerminalStripModel::bridgePixmapFor(const QModelIndex &index) const { if (!index.isValid() || m_terminal_strip.isNull()) { return QPixmap(); } auto level_column = levelForColumn(columnTypeForIndex(index)); if (level_column == -1) { return QPixmap(); } auto mrtd = modelRealTerminalDataForIndex(index); //Terminal level correspond to the column level of index if (level_column == mrtd.level_) { if (mrtd.bridged_) { auto bridge_ = m_terminal_strip->isBridged(mrtd.real_terminal); if (bridge_) { const auto previous_t = m_terminal_strip->previousTerminalInLevel(mrtd.real_terminal); QSharedPointer previous_bridge; if (previous_t) previous_bridge = previous_t->bridge(); const auto next_t = m_terminal_strip->nextTerminalInLevel(mrtd.real_terminal); QSharedPointer next_bridge; if (next_t) next_bridge = next_t->bridge(); auto color_ = bridge_->color(); auto pixmap_ = m_bridges_pixmaps.value(color_); //Current real terminal between two bridged terminal if ((bridge_ == previous_bridge) && (bridge_ == next_bridge)) { return pixmap_.middle_; } else if (bridge_ == previous_bridge) { return pixmap_.bottom_; } else if (bridge_ == next_bridge) { return pixmap_.top_; } } } } //Terminal level ins't in the same column level of index //Check if we need to draw a none bridge pixmap //Check previous auto phy_t = mrtd.real_terminal.toStrongRef()->physicalTerminal(); auto current_real_terminal = mrtd; auto current_phy_uuid = phy_t->uuid(); bool already_jumped_to_previous = false; modelRealTerminalData previous_data; do { current_real_terminal = modelRealTerminalData::data(m_terminal_strip->previousRealTerminal(current_real_terminal.real_terminal)); if (current_real_terminal.level_ == -1) { break; } //We are in the same physical terminal as previous loop if (current_phy_uuid == current_real_terminal.real_terminal.toStrongRef()->physicalTerminal()->uuid()) { if (current_real_terminal.bridged_ && current_real_terminal.level_ == level_column) { previous_data = current_real_terminal; break; } } else if (already_jumped_to_previous) { //We are not in same physical terminal as previous loop break; } else { already_jumped_to_previous = true; current_phy_uuid = current_real_terminal.real_terminal.toStrongRef()->physicalTerminal()->uuid(); if (current_real_terminal.bridged_ && current_real_terminal.level_ == level_column) { previous_data = current_real_terminal; break; } } } while(true); //Check next current_real_terminal = mrtd; current_phy_uuid = phy_t->uuid(); bool already_jumped_to_next = false; modelRealTerminalData next_data; do { current_real_terminal = modelRealTerminalData::data(m_terminal_strip->nextRealTerminal(current_real_terminal.real_terminal)); if (current_real_terminal.level_ == -1) { break; } //We are in the same physical terminal as previous loop if (current_phy_uuid == current_real_terminal.real_terminal.toStrongRef()->physicalTerminal()->uuid()) { if (current_real_terminal.bridged_ && current_real_terminal.level_ == level_column) { next_data = current_real_terminal; break; } } else if (already_jumped_to_next) { //We are not in same physical terminal as previous loop break; } else { already_jumped_to_next = true; current_phy_uuid = current_real_terminal.real_terminal.toStrongRef()->physicalTerminal()->uuid(); if (current_real_terminal.bridged_ && current_real_terminal.level_ == level_column) { next_data = current_real_terminal; break; } } } while(true); auto previous_bridge = m_terminal_strip->isBridged(previous_data.real_terminal); if (previous_bridge == m_terminal_strip->isBridged(next_data.real_terminal)) { if (previous_bridge) { return m_bridges_pixmaps.value(previous_bridge->color()).none_; } } return QPixmap(); } /*********************************************************** * A little delegate for add a combobox to edit type * and a spinbox to edit the level of a terminal **********************************************************/ TerminalStripModelDelegate::TerminalStripModelDelegate(QObject *parent) : QStyledItemDelegate(parent) {} QWidget *TerminalStripModelDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const { if (index.column() == TYPE_CELL) { auto qcb = new QComboBox(parent); qcb->setObjectName(QLatin1String("terminal_type")); qcb->addItem(ElementData::translatedTerminalType(ElementData::TTGeneric), ElementData::TTGeneric); qcb->addItem(ElementData::translatedTerminalType(ElementData::TTFuse), ElementData::TTFuse); qcb->addItem(ElementData::translatedTerminalType(ElementData::TTSectional), ElementData::TTSectional); qcb->addItem(ElementData::translatedTerminalType(ElementData::TTDiode), ElementData::TTDiode); qcb->addItem(ElementData::translatedTerminalType(ElementData::TTGround), ElementData::TTGround); return qcb; } if (index.column() == FUNCTION_CELL) { auto qcb = new QComboBox(parent); qcb->setObjectName(QLatin1String("terminal_function")); qcb->addItem(ElementData::translatedTerminalFunction(ElementData::TFGeneric), ElementData::TFGeneric); qcb->addItem(ElementData::translatedTerminalFunction(ElementData::TFPhase), ElementData::TFPhase); qcb->addItem(ElementData::translatedTerminalFunction(ElementData::TFNeutral), ElementData::TFNeutral); return qcb; } return QStyledItemDelegate::createEditor(parent, option, index); } void TerminalStripModelDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const { if (index.isValid()) { if (editor->objectName() == QLatin1String("terminal_type")) { if (auto qcb = dynamic_cast(editor)) { model->setData(index, qcb->currentData(), Qt::EditRole); } } else if (editor->objectName() == QLatin1String("terminal_function")) { if (auto qcb = dynamic_cast(editor)) { model->setData(index, qcb->currentData(), Qt::EditRole); } } else { QStyledItemDelegate::setModelData(editor, model, index); } } } /** * @brief TerminalStripModelDelegate::paint * By default on a QTableView, Qt draw pixmap in cell with a little margin at left. * Override the function to draw the pixmap of bridge without the margin at left. * @param painter * @param option * @param index */ void TerminalStripModelDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { auto column = index.column(); if (column == LEVEL_0_CELL || column == LEVEL_1_CELL || column == LEVEL_2_CELL || column == LEVEL_3_CELL) { auto variant = index.data(Qt::DecorationRole); if (variant.isNull()) { QStyledItemDelegate::paint(painter, option, index); } else { if (option.state & QStyle::State_Selected) { QStyleOptionViewItem opt_ = option; initStyleOption(&opt_, index); QStyle *style = QApplication::style(); auto px = style->generatedIconPixmap(QIcon::Selected, variant.value(), &opt_); style->drawItemPixmap(painter, option.rect, Qt::AlignLeft, px); } else { painter->drawPixmap(option.rect, variant.value()); } } } else { QStyledItemDelegate::paint(painter, option, index); } }