#include "simplelisttablecontroller.h" #include "../model/componentmodel.h" #include "../model/specificationpcbtablemodel.h" #include #include #include SimpleListTableController::SimpleListTableController(QObject *parent) : QObject(parent) , m_tableModel(new SimpleListTableModel(this)) , m_nameField("Name") , m_techReservePercent(10) , m_boardsCount(1) { qDebug() << "SimpleListTableController::SimpleListTableController: Создан контроллер"; } SimpleListTableController::~SimpleListTableController() { // m_tableModel удалится автоматически, так как он является дочерним объектом } void SimpleListTableController::setModel(SimpleListTableModel *model) { if (m_tableModel != model) { qDebug() << "SimpleListTableController::setModel: Заменяем модель таблицы"; m_tableModel = model; // Синхронизируем процент тех запаса с моделью if (m_tableModel) { m_tableModel->setTechReservePercent(m_techReservePercent); } } } void SimpleListTableController::setComponents(const QList &components) { qDebug() << "SimpleListTableController::setComponents: Устанавливаем" << components.size() << "компонентов"; m_components = components; // Собираем доступные свойства из компонентов m_propertyNames = collectPropertyNames(); qDebug() << "SimpleListTableController::setComponents: Собрано свойств:" << m_propertyNames.size(); } void SimpleListTableController::generateTableFromComponents() { if (!m_tableModel) { return; } qDebug() << "SimpleListTableController::generateTableFromComponents: Начинаем генерацию таблицы"; qDebug() << "SimpleListTableController::generateTableFromComponents: Количество компонентов:" << m_components.size(); // Очищаем таблицу m_tableModel->clear(); if (m_components.isEmpty()) { return; } try { // Сортируем компоненты по десигнатору std::sort(m_components.begin(), m_components.end(), [this](ComponentModel* a, ComponentModel* b) { if (!a || !b) return false; QString designatorA = a->designator(); QString designatorB = b->designator(); // Разделяем десигнаторы на буквенную и числовую части QPair partsA = splitDesignator(designatorA); QPair partsB = splitDesignator(designatorB); QString letterA = partsA.first; QString letterB = partsB.first; QString numberA = partsA.second; QString numberB = partsB.second; // Сначала сравниваем буквенные части if (letterA != letterB) { return letterA < letterB; } // Если буквенные части одинаковые, сравниваем числовые части bool okA, okB; int numA = numberA.toInt(&okA); int numB = numberB.toInt(&okB); if (okA && okB) { return numA < numB; } // Fallback: если не удалось преобразовать в числа, используем строковое сравнение return numberA < numberB; }); // Группируем компоненты по наименованию (Name) QString prevName = ""; QString groupStartDesignator = ""; QString groupLastDesignator = ""; int groupCount = 0; for (int i = 0; i < m_components.size(); ++i) { ComponentModel *component = m_components[i]; if (!component) { qDebug() << "SimpleListTableController::generateTableFromComponents: Пропускаем nullptr компонент на позиции" << i; continue; } QString currentName = getComponentValue(component, m_nameField); if (i == 0 || currentName != prevName) { // Добавляем предыдущую группу, если она есть if (i > 0 && groupCount > 0) { SimpleListRowData row; // Формируем строку десигнаторов QString designatorText; if (groupCount == 1) { designatorText = groupStartDesignator; } else if (groupCount == 2) { designatorText = groupStartDesignator + ", " + groupLastDesignator; } else { designatorText = groupStartDesignator + "-" + groupLastDesignator; } row.designator = SimpleListCellData(designatorText, false, false); row.name = SimpleListCellData(prevName, false, false); // (кол-во элементов * кол-во плат) + техзапас int baseQty = groupCount * m_boardsCount; int reserve = qMax(1, qRound(baseQty * m_techReservePercent / 100.0)); int quantityWithReserve = baseQty + reserve; row.quantity = SimpleListCellData(QString::number(quantityWithReserve), false, false); if (m_tableModel) { m_tableModel->addRow(row); } } // Начинаем новую группу groupStartDesignator = component->designator(); groupLastDesignator = component->designator(); groupCount = 1; prevName = currentName; } else { // Продолжаем текущую группу - обновляем последний десигнатор groupLastDesignator = component->designator(); groupCount++; } } // Добавляем последнюю группу if (groupCount > 0 && m_tableModel) { SimpleListRowData row; QString designatorText; if (groupCount == 1) { designatorText = groupStartDesignator; } else if (groupCount == 2) { designatorText = groupStartDesignator + ", " + groupLastDesignator; } else { designatorText = groupStartDesignator + "-" + groupLastDesignator; } row.designator = SimpleListCellData(designatorText, false, false); row.name = SimpleListCellData(prevName, false, false); // (кол-во элементов * кол-во плат) + техзапас int baseQty = groupCount * m_boardsCount; int reserve = qMax(1, qRound(baseQty * m_techReservePercent / 100.0)); int quantityWithReserve = baseQty + reserve; row.quantity = SimpleListCellData(QString::number(quantityWithReserve), false, false); m_tableModel->addRow(row); } } catch (const std::exception &e) { qDebug() << "SimpleListTableController::generateTableFromComponents: Ошибка:" << e.what(); } qDebug() << "SimpleListTableController::generateTableFromComponents: Генерация завершена"; } void SimpleListTableController::generateTableFromSpecificationPCB(SpecificationPCBTableModel *specPCBModel) { if (!m_tableModel || !specPCBModel) { qDebug() << "SimpleListTableController::generateTableFromSpecificationPCB: Модель недоступна"; return; } qDebug() << "SimpleListTableController::generateTableFromSpecificationPCB: Начинаем генерацию из SpecificationPCB"; // Очищаем таблицу m_tableModel->clear(); // Получаем все строки из SpecificationPCB QList allRows = specPCBModel->getRows(); if (allRows.isEmpty()) { qDebug() << "SimpleListTableController::generateTableFromSpecificationPCB: Нет данных в SpecificationPCB"; return; } // Находим индекс строки с "Прочие изделия" int startIndex = -1; for (int i = 0; i < allRows.size(); ++i) { if (allRows[i].name.value == "Прочие изделия" && allRows[i].isHeader) { startIndex = i; break; } } if (startIndex == -1) { qDebug() << "SimpleListTableController::generateTableFromSpecificationPCB: Не найдена строка 'Прочие изделия'"; return; } // Находим индекс строки с "Примечание" int endIndex = -1; for (int i = startIndex + 1; i < allRows.size(); ++i) { if (allRows[i].name.value == "Примечание" && !allRows[i].isHeader) { endIndex = i; break; } } if (endIndex == -1) { qDebug() << "SimpleListTableController::generateTableFromSpecificationPCB: Не найдена строка 'Примечание'"; // Если не найдено "Примечание", берем все строки до конца endIndex = allRows.size(); } qDebug() << "SimpleListTableController::generateTableFromSpecificationPCB: Найдены строки от" << startIndex << "до" << endIndex; // Извлекаем строки между "Прочие изделия" и "Примечание" // Пропускаем саму строку "Прочие изделия" и пустые строки после нее QList relevantRows; for (int i = startIndex + 1; i < endIndex; ++i) { // Пропускаем пустые строки if (allRows[i].isEmpty) { continue; } // Пропускаем заголовки if (allRows[i].isHeader) { continue; } relevantRows.append(allRows[i]); } if (relevantRows.isEmpty()) { qDebug() << "SimpleListTableController::generateTableFromSpecificationPCB: Нет релевантных строк"; return; } qDebug() << "SimpleListTableController::generateTableFromSpecificationPCB: Найдено релевантных строк:" << relevantRows.size(); // Группируем строки по позиции (position) // Строки с одинаковой позицией должны быть склеены вместе // Используем QMap для группировки, но обрабатываем в порядке появления QMap> groupedByPosition; QStringList positionOrder; // Для сохранения порядка позиций QString currentPosition; for (const SpecificationPCBRowData &row : relevantRows) { QString position = row.position.value.trimmed(); // Если позиция пуста, это продолжение предыдущей позиции if (position.isEmpty()) { // Добавляем к текущей позиции if (!currentPosition.isEmpty()) { groupedByPosition[currentPosition].append(row); } } else { // Новая позиция currentPosition = position; if (!groupedByPosition.contains(position)) { groupedByPosition[position] = QList(); positionOrder.append(position); // Сохраняем порядок } groupedByPosition[position].append(row); } } qDebug() << "SimpleListTableController::generateTableFromSpecificationPCB: Сгруппировано позиций:" << groupedByPosition.size(); // Обрабатываем каждую группу позиций в порядке появления for (const QString &position : positionOrder) { if (!groupedByPosition.contains(position)) { continue; } QList rowsForPosition = groupedByPosition[position]; if (rowsForPosition.isEmpty()) { continue; } // Склеиваем наименование (name) и примечание (note) для всех строк этой позиции QStringList nameParts; QStringList noteParts; for (const SpecificationPCBRowData &row : rowsForPosition) { QString nameValue = row.name.value.trimmed(); QString noteValue = row.note.value.trimmed(); if (!nameValue.isEmpty()) { nameParts.append(nameValue); } // Примечание (десигнаторы): разбиваем по переносам строк внутри ячейки (R1, R3\nR5 -> R1, R3, R5) if (!noteValue.isEmpty()) { QString normalized = noteValue.replace("\r\n", "\n").replace('\r', '\n'); for (const QString &line : normalized.split('\n', Qt::SkipEmptyParts)) { QString trimmed = line.trimmed(); if (!trimmed.isEmpty()) { noteParts.append(trimmed); } } } } // Объединяем части наименования и примечания обратно в одну строку QString fullName = nameParts.join(" "); // Склеиваем многострочные наименования QString fullNote = noteParts.join(", "); // Десигнаторы (R1, R3, R5) — между частями после переноса ставим запятую // Получаем количество из первой строки позиции (остальные строки обычно пустые по количеству) int quantity = 0; for (const SpecificationPCBRowData &row : rowsForPosition) { QString qtyStr = row.quantity.value.trimmed(); if (!qtyStr.isEmpty()) { bool ok; int qty = qtyStr.toInt(&ok); if (ok && qty > 0) { quantity = qty; break; } } } // Если количество не найдено, считаем количество строк с номерами позиций if (quantity == 0) { quantity = 1; // По умолчанию 1 } // (кол-во элементов * кол-во плат) + техзапас int baseQty = quantity * m_boardsCount; int reserve = qMax(1, qRound(baseQty * m_techReservePercent / 100.0)); int quantityWithReserve = baseQty + reserve; // Создаем строку для SimpleList SimpleListRowData row; row.designator = SimpleListCellData(fullNote, false, false); // Поз.обозначение из примечания (десигнаторы) row.name = SimpleListCellData(fullName, false, false); // Наименование (склеенное из всех строк позиции) row.quantity = SimpleListCellData(QString::number(quantityWithReserve), false, false); m_tableModel->addRow(row); qDebug() << "SimpleListTableController::generateTableFromSpecificationPCB: Добавлена строка - Позиция:" << position << "Поз.обозначение:" << fullNote << "Наименование:" << fullName << "Кол-во:" << quantityWithReserve; } qDebug() << "SimpleListTableController::generateTableFromSpecificationPCB: Генерация завершена, строк:" << m_tableModel->rowCount(); } void SimpleListTableController::updateTableFromComponents() { generateTableFromComponents(); } void SimpleListTableController::addEmptyRow() { if (!m_tableModel) { return; } m_tableModel->addEmptyRow(); } void SimpleListTableController::addEmptyRowAt(int position) { if (!m_tableModel) { return; } m_tableModel->addEmptyRowAt(position); } void SimpleListTableController::insertEmptyRowAt(int position) { if (!m_tableModel) { return; } m_tableModel->addEmptyRowAt(position); } void SimpleListTableController::removeRow(int row) { if (!m_tableModel) { return; } m_tableModel->removeRow(row); } void SimpleListTableController::clearTable() { if (!m_tableModel) { return; } m_tableModel->clear(); } QString SimpleListTableController::getComponentValue(ComponentModel *component, const QString &propertyName) { if (!component) { return ""; } // Получаем значение свойства компонента QString value; if (propertyName == "Designator") { value = component->designator(); } else { QMap props = component->properties(); value = props.value(propertyName, ""); } // Проверяем, является ли значение свойства выражением if (value.startsWith("=")) { qDebug() << "SimpleListTableController::getComponentValue: Значение свойства является выражением:" << value; return parseExpression(component, value); } return value; } QString SimpleListTableController::parseExpression(ComponentModel *component, const QString &expression) { if (!component) { return ""; } qDebug() << "SimpleListTableController::parseExpression: Обрабатываем выражение:" << expression; QString result; QString expr; // Определяем, как обрабатывать выражение if (expression.startsWith("=")) { expr = expression.mid(1); // Убираем начальный "=" } else if (expression.startsWith("\"") && expression.endsWith("\"")) { // Если это просто значение в кавычках, возвращаем его без кавычек return expression.mid(1, expression.length() - 2); } else { // Если это не выражение, возвращаем как есть return expression; } // Разбиваем выражение по операторам "+" с учетом кавычек QStringList parts; QString currentPart; bool inDoubleQuotes = false; bool inSingleQuotes = false; for (int i = 0; i < expr.length(); ++i) { QChar ch = expr[i]; if (ch == '"' && !inSingleQuotes) { inDoubleQuotes = !inDoubleQuotes; currentPart += ch; } else if (ch == '\'' && !inDoubleQuotes) { inSingleQuotes = !inSingleQuotes; currentPart += ch; } else if (ch == '+' && !inDoubleQuotes && !inSingleQuotes) { // Это разделитель "+" вне кавычек if (!currentPart.trimmed().isEmpty()) { parts.append(currentPart.trimmed()); } currentPart.clear(); } else { currentPart += ch; } } // Добавляем последнюю часть if (!currentPart.trimmed().isEmpty()) { parts.append(currentPart.trimmed()); } qDebug() << "SimpleListTableController::parseExpression: Части выражения:" << parts; for (const QString &part : parts) { QString trimmedPart = part.trimmed(); qDebug() << "SimpleListTableController::parseExpression: Обрабатываем часть:" << trimmedPart; // Если это не пустая строка, добавляем к результату if (!trimmedPart.isEmpty()) { QString value; // Проверяем, является ли это литералом в одинарных кавычках if (trimmedPart.startsWith("'") && trimmedPart.endsWith("'")) { // Это литерал - убираем кавычки и используем как есть value = trimmedPart.mid(1, trimmedPart.length() - 2); qDebug() << "SimpleListTableController::parseExpression: Найден литерал в одинарных кавычках:" << value; } // Проверяем, является ли это именем свойства компонента в двойных кавычках else if (trimmedPart.startsWith("\"") && trimmedPart.endsWith("\"")) { // Это свойство компонента - убираем кавычки и ищем в свойствах QString propertyName = trimmedPart.mid(1, trimmedPart.length() - 2); qDebug() << "SimpleListTableController::parseExpression: Ищем свойство компонента:" << propertyName; if (propertyName == "Designator") { value = component->designator(); qDebug() << "SimpleListTableController::parseExpression: Найдено свойство Designator =" << value; } else { // Ищем в свойствах компонента QMap props = component->properties(); value = props.value(propertyName, ""); qDebug() << "SimpleListTableController::parseExpression: Ищем свойство" << propertyName << "в компоненте, результат:" << value; // Рекурсивно обрабатываем вложенные выражения if (value.startsWith("=")) { qDebug() << "SimpleListTableController::parseExpression: Значение свойства является вложенным выражением, рекурсивно обрабатываем"; value = parseExpression(component, value); } } } // Проверяем, является ли это именем свойства компонента без кавычек else if (trimmedPart == "Designator") { value = component->designator(); qDebug() << "SimpleListTableController::parseExpression: Найдено свойство Designator =" << value; } else { // Ищем в свойствах компонента QMap props = component->properties(); value = props.value(trimmedPart, ""); qDebug() << "SimpleListTableController::parseExpression: Ищем свойство" << trimmedPart << "в компоненте, результат:" << value; // Рекурсивно обрабатываем вложенные выражения if (value.startsWith("=")) { qDebug() << "SimpleListTableController::parseExpression: Значение свойства является вложенным выражением, рекурсивно обрабатываем"; value = parseExpression(component, value); } } // Добавляем значение к результату if (!value.isEmpty()) { if (!result.isEmpty()) { result += value; } else { result = value; } qDebug() << "SimpleListTableController::parseExpression: Добавили к результату, текущий результат:" << result; } } } qDebug() << "SimpleListTableController::parseExpression: Финальный результат:" << result; return result; } QStringList SimpleListTableController::collectPropertyNames() { QStringList propertyNames; // Добавляем основные свойства компонента propertyNames << "Designator"; // Собираем дополнительные свойства из компонентов for (ComponentModel *component : m_components) { QMap props = component->properties(); for (auto it = props.begin(); it != props.end(); ++it) { if (!propertyNames.contains(it.key())) { propertyNames.append(it.key()); } } } // Если дополнительные свойства не найдены в компонентах, добавляем стандартные if (propertyNames.size() == 1) { // Только Designator propertyNames << "Name" << "Type" << "Value" << "Footprint" << "Description"; } return propertyNames; } QPair SimpleListTableController::splitDesignator(const QString &designator) { if (designator.isEmpty()) { return QPair("", ""); } // Используем регулярное выражение для разделения буквенной и цифровой части QRegExp regex("([A-Za-z]+)(\\d*)"); if (regex.indexIn(designator) != -1) { QString letterPart = regex.cap(1); QString numberPart = regex.cap(2); return QPair(letterPart, numberPart); } // Fallback: если регулярное выражение не сработало, // ищем первую цифру и разделяем по ней for (int i = 0; i < designator.length(); ++i) { if (designator[i].isDigit()) { QString letterPart = designator.left(i); QString numberPart = designator.mid(i); return QPair(letterPart, numberPart); } } // Если цифр нет, возвращаем весь десигнатор как буквенную часть return QPair(designator, ""); } void SimpleListTableController::setNameField(const QString &fieldName) { qDebug() << "SimpleListTableController::setNameField: Устанавливаем поле наименования:" << fieldName; m_nameField = fieldName; } QString SimpleListTableController::getNameField() const { return m_nameField; } void SimpleListTableController::setTechReservePercent(int percent) { qDebug() << "SimpleListTableController::setTechReservePercent: Устанавливаем процент тех запаса:" << percent; m_techReservePercent = percent; // Синхронизируем с моделью для обновления заголовка if (m_tableModel) { m_tableModel->setTechReservePercent(percent); } } int SimpleListTableController::getTechReservePercent() const { return m_techReservePercent; } void SimpleListTableController::setBoardsCount(int count) { if (count < 1) { count = 1; } m_boardsCount = count; } int SimpleListTableController::getBoardsCount() const { return m_boardsCount; } QStringList SimpleListTableController::getAvailableProperties() const { return m_propertyNames; }