Files
GostGenerator/model/specificationpcbtablemodel.cpp
T

1346 lines
52 KiB
C++

#include "specificationpcbtablemodel.h"
#include "componentmodel.h"
#include "../view/pagebreakconfirmationdialog.h"
#include <QDebug>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QColor>
#include <QApplication>
#include <QFont>
#include <QFontMetrics>
#include <QSet>
#include <QPair>
#include <QBrush>
// Статические константы
const QStringList SpecificationPCBTableModel::DEFAULT_HEADERS = {
"Формат", "Зона", "Поз.", "Обозначение", "Наименование", "Кол.", "Примечание"
};
SpecificationPCBTableModel::SpecificationPCBTableModel(QObject *parent)
: QAbstractTableModel(parent)
, m_columnMappings(DEFAULT_HEADERS)
{
qDebug() << "SpecificationPCBTableModel: Конструктор";
}
int SpecificationPCBTableModel::rowCount(const QModelIndex &parent) const
{
if (parent.isValid()) {
return 0;
}
return m_rows.size();
}
int SpecificationPCBTableModel::columnCount(const QModelIndex &parent) const
{
if (parent.isValid()) {
return 0;
}
return COLUMN_COUNT;
}
QVariant SpecificationPCBTableModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid() || index.row() >= m_rows.size() || index.column() >= COLUMN_COUNT) {
return QVariant();
}
const SpecificationPCBRowData &rowData = m_rows[index.row()];
if (role == Qt::DisplayRole || role == Qt::EditRole) {
switch (index.column()) {
case 0: return rowData.format.value;
case 1: return rowData.zone.value;
case 2: return rowData.position.value;
case 3: return rowData.designation.value;
case 4: return rowData.name.value;
case 5: return rowData.quantity.value;
case 6: return rowData.note.value;
default: return QVariant();
}
}
if (role == Qt::BackgroundRole) {
if (rowData.isHeader) {
return QBrush(QColor(200, 200, 200));
} else {
// Проверяем переполнение ячейки (используем приблизительную ширину колонки)
// Приблизительные ширины колонок для SpecificationPCB (в пикселях)
static const qreal columnWidths[] = {60, 50, 80, 150, 200, 60, 150}; // Формат, Зона, Поз., Обозначение, Наименование, Кол., Примечание
qreal columnWidth = (index.column() < 7) ? columnWidths[index.column()] : 150;
// Проверяем переполнение ячейки (используем информацию из PDF)
QPair<int, int> cellKey(index.row(), index.column());
if (m_overflowCells.contains(cellKey)) {
// Подсвечиваем красным, если ячейка переполнена (помечена из PDF)
return QBrush(QColor(255, 200, 200)); // Светло-красный
}
// Чередуем оттенки фона для разных страниц
// Нечетные страницы - светло-голубой, четные - светло-желтый
if (rowData.pageNumber % 2 == 1) {
return QBrush(QColor(240, 248, 255)); // AliceBlue - для нечетных страниц
} else {
return QBrush(QColor(255, 255, 240)); // Ivory - для четных страниц
}
}
}
if (role == Qt::TextAlignmentRole) {
return QVariant(Qt::AlignLeft | Qt::AlignVCenter);
} else if (role == Qt::FontRole) {
QFont font;
bool isBold = false;
bool isUnderline = false;
// Проверяем свойства ячейки
switch (index.column()) {
case 0:
isBold = rowData.format.isHeader;
isUnderline = rowData.format.isUnderline;
break;
case 1:
isBold = rowData.zone.isHeader;
isUnderline = rowData.zone.isUnderline;
break;
case 2:
isBold = rowData.position.isHeader;
isUnderline = rowData.position.isUnderline;
break;
case 3:
isBold = rowData.designation.isHeader;
isUnderline = rowData.designation.isUnderline;
break;
case 4:
isBold = rowData.name.isHeader;
isUnderline = rowData.name.isUnderline;
break;
case 5:
isBold = rowData.quantity.isHeader;
isUnderline = rowData.quantity.isUnderline;
break;
case 6:
isBold = rowData.note.isHeader;
isUnderline = rowData.note.isUnderline;
break;
default:
break;
}
if (isBold) {
font.setBold(true);
}
if (isUnderline) {
font.setUnderline(true);
}
if (isBold || isUnderline) {
return font;
}
} else if (role == Qt::UserRole) {
// Возвращаем структуру ячейки для дополнительной информации
switch (index.column()) {
case 0: return QVariant::fromValue(rowData.format);
case 1: return QVariant::fromValue(rowData.zone);
case 2: return QVariant::fromValue(rowData.position);
case 3: return QVariant::fromValue(rowData.designation);
case 4: return QVariant::fromValue(rowData.name);
case 5: return QVariant::fromValue(rowData.quantity);
case 6: return QVariant::fromValue(rowData.note);
default: return QVariant();
}
}
return QVariant();
}
QVariant SpecificationPCBTableModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (role == Qt::DisplayRole) {
if (orientation == Qt::Horizontal) {
if (section < DEFAULT_HEADERS.size()) {
return DEFAULT_HEADERS[section];
}
} else {
// Вертикальные заголовки - номера строк с указанием страницы
if (section < m_rows.size()) {
const SpecificationPCBRowData &row = m_rows[section];
return QString("%1 (стр. %2)").arg(section + 1).arg(row.pageNumber);
} else {
return section + 1;
}
}
}
return QVariant();
}
bool SpecificationPCBTableModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (!index.isValid() || index.row() >= m_rows.size() || index.column() >= COLUMN_COUNT) {
return false;
}
if (role == Qt::EditRole) {
SpecificationPCBRowData &rowData = m_rows[index.row()];
// Проверяем, можно ли редактировать эту строку
if (!canEditRow(index.row())) {
return false;
}
QString stringValue = value.toString();
switch (index.column()) {
case 0:
rowData.format.value = stringValue;
rowData.format.isAutoGenerated = false;
rowData.format.isEmpty = stringValue.isEmpty();
break;
case 1:
rowData.zone.value = stringValue;
rowData.zone.isAutoGenerated = false;
rowData.zone.isEmpty = stringValue.isEmpty();
break;
case 2:
rowData.position.value = stringValue;
rowData.position.isAutoGenerated = false;
rowData.position.isEmpty = stringValue.isEmpty();
break;
case 3:
rowData.designation.value = stringValue;
rowData.designation.isAutoGenerated = false;
rowData.designation.isEmpty = stringValue.isEmpty();
break;
case 4:
rowData.name.value = stringValue;
rowData.name.isAutoGenerated = false;
rowData.name.isEmpty = stringValue.isEmpty();
break;
case 5:
rowData.quantity.value = stringValue;
rowData.quantity.isAutoGenerated = false;
rowData.quantity.isEmpty = stringValue.isEmpty();
break;
case 6:
rowData.note.value = stringValue;
rowData.note.isAutoGenerated = false;
rowData.note.isEmpty = stringValue.isEmpty();
break;
default: return false;
}
// Обновляем статус пустой строки
updateRowEmptyStatus(index.row());
emit dataChanged(index, index);
emit modelDataChanged();
return true;
}
return false;
}
bool SpecificationPCBTableModel::setCellStretch(int row, int column, int stretch)
{
if (row < 0 || row >= m_rows.size() || column < 0 || column >= COLUMN_COUNT) {
return false;
}
SpecificationPCBRowData &rowData = m_rows[row];
bool changed = false;
switch (column) {
case 0:
if (rowData.format.stretch != stretch) {
rowData.format.stretch = stretch;
changed = true;
}
break;
case 1:
if (rowData.zone.stretch != stretch) {
rowData.zone.stretch = stretch;
changed = true;
}
break;
case 2:
if (rowData.position.stretch != stretch) {
rowData.position.stretch = stretch;
changed = true;
}
break;
case 3:
if (rowData.designation.stretch != stretch) {
rowData.designation.stretch = stretch;
changed = true;
}
break;
case 4:
if (rowData.name.stretch != stretch) {
rowData.name.stretch = stretch;
changed = true;
}
break;
case 5:
if (rowData.quantity.stretch != stretch) {
rowData.quantity.stretch = stretch;
changed = true;
}
break;
case 6:
if (rowData.note.stretch != stretch) {
rowData.note.stretch = stretch;
changed = true;
}
break;
default:
return false;
}
if (changed) {
QModelIndex index = createIndex(row, column);
emit dataChanged(index, index, QVector<int>() << Qt::UserRole);
return true;
}
return false;
}
bool SpecificationPCBTableModel::setCellHeader(int row, int column, bool isHeader)
{
if (row < 0 || row >= m_rows.size() || column < 0 || column >= COLUMN_COUNT) {
return false;
}
SpecificationPCBRowData &rowData = m_rows[row];
bool changed = false;
switch (column) {
case 0:
if (rowData.format.isHeader != isHeader) {
rowData.format.isHeader = isHeader;
changed = true;
}
break;
case 1:
if (rowData.zone.isHeader != isHeader) {
rowData.zone.isHeader = isHeader;
changed = true;
}
break;
case 2:
if (rowData.position.isHeader != isHeader) {
rowData.position.isHeader = isHeader;
changed = true;
}
break;
case 3:
if (rowData.designation.isHeader != isHeader) {
rowData.designation.isHeader = isHeader;
changed = true;
}
break;
case 4:
if (rowData.name.isHeader != isHeader) {
rowData.name.isHeader = isHeader;
changed = true;
}
break;
case 5:
if (rowData.quantity.isHeader != isHeader) {
rowData.quantity.isHeader = isHeader;
changed = true;
}
break;
case 6:
if (rowData.note.isHeader != isHeader) {
rowData.note.isHeader = isHeader;
changed = true;
}
break;
default:
return false;
}
if (changed) {
QModelIndex index = createIndex(row, column);
emit dataChanged(index, index, QVector<int>() << Qt::UserRole);
return true;
}
return false;
}
bool SpecificationPCBTableModel::setCellUnderline(int row, int column, bool isUnderline)
{
if (row < 0 || row >= m_rows.size() || column < 0 || column >= COLUMN_COUNT) {
return false;
}
SpecificationPCBRowData &rowData = m_rows[row];
bool changed = false;
switch (column) {
case 0:
if (rowData.format.isUnderline != isUnderline) {
rowData.format.isUnderline = isUnderline;
changed = true;
}
break;
case 1:
if (rowData.zone.isUnderline != isUnderline) {
rowData.zone.isUnderline = isUnderline;
changed = true;
}
break;
case 2:
if (rowData.position.isUnderline != isUnderline) {
rowData.position.isUnderline = isUnderline;
changed = true;
}
break;
case 3:
if (rowData.designation.isUnderline != isUnderline) {
rowData.designation.isUnderline = isUnderline;
changed = true;
}
break;
case 4:
if (rowData.name.isUnderline != isUnderline) {
rowData.name.isUnderline = isUnderline;
changed = true;
}
break;
case 5:
if (rowData.quantity.isUnderline != isUnderline) {
rowData.quantity.isUnderline = isUnderline;
changed = true;
}
break;
case 6:
if (rowData.note.isUnderline != isUnderline) {
rowData.note.isUnderline = isUnderline;
changed = true;
}
break;
default:
return false;
}
if (changed) {
QModelIndex index = createIndex(row, column);
emit dataChanged(index, index, QVector<int>() << Qt::UserRole);
return true;
}
return false;
}
bool SpecificationPCBTableModel::isCellOverflow(int row, int column, qreal columnWidth, int fontSize) const
{
if (row < 0 || row >= m_rows.size() || column < 0 || column >= COLUMN_COUNT) {
return false;
}
const SpecificationPCBRowData &rowData = m_rows[row];
QString cellText;
int cellStretch = 100;
// Получаем текст и поджим ячейки
switch (column) {
case 0: cellText = rowData.format.value; cellStretch = rowData.format.stretch; break;
case 1: cellText = rowData.zone.value; cellStretch = rowData.zone.stretch; break;
case 2: cellText = rowData.position.value; cellStretch = rowData.position.stretch; break;
case 3: cellText = rowData.designation.value; cellStretch = rowData.designation.stretch; break;
case 4: cellText = rowData.name.value; cellStretch = rowData.name.stretch; break;
case 5: cellText = rowData.quantity.value; cellStretch = rowData.quantity.stretch; break;
case 6: cellText = rowData.note.value; cellStretch = rowData.note.stretch; break;
default: return false;
}
if (cellText.isEmpty()) {
return false;
}
// Используем ту же логику, что и в PDF: масштабирование вместо setStretch
// Создаем шрифт без поджима для измерения ширины
QFont testFont("Arial", fontSize); // Используем Arial как приближение
QFontMetrics metrics(testFont);
// Вычисляем коэффициент масштабирования (как в drawCellWithStretch)
qreal scaleX = cellStretch / 100.0;
// Вычисляем ширину текста после масштабирования
qreal textWidth = metrics.horizontalAdvance(cellText) * scaleX;
// Используем ту же логику, что и в PDF: если текст обрезается с многоточием, это переполнение
// Вычисляем максимальную ширину текста до масштабирования (как в drawCellWithStretch)
qreal maxTextWidth = columnWidth / scaleX;
// Проверяем, будет ли текст обрезан с многоточием (как в PDF)
// Если исходный текст шире, чем максимальная ширина, то он будет обрезан
qreal originalTextWidth = metrics.horizontalAdvance(cellText);
// Добавляем небольшой запас для учета возможных расхождений между Arial и GOST шрифтами
qreal tolerance = 3.0;
// Переполнение, если текст будет обрезан с многоточием
return originalTextWidth > (maxTextWidth + tolerance);
}
void SpecificationPCBTableModel::setCellOverflow(int row, int column, bool overflow)
{
if (row < 0 || row >= m_rows.size() || column < 0 || column >= COLUMN_COUNT) {
return;
}
QPair<int, int> cellKey(row, column);
if (overflow) {
if (!m_overflowCells.contains(cellKey)) {
m_overflowCells.insert(cellKey);
QModelIndex index = createIndex(row, column);
emit dataChanged(index, index, QVector<int>() << Qt::BackgroundRole);
}
} else {
if (m_overflowCells.contains(cellKey)) {
m_overflowCells.remove(cellKey);
QModelIndex index = createIndex(row, column);
emit dataChanged(index, index, QVector<int>() << Qt::BackgroundRole);
}
}
}
void SpecificationPCBTableModel::clearOverflowMarks()
{
if (m_overflowCells.isEmpty()) {
return;
}
// Сохраняем индексы для обновления
QList<QModelIndex> indicesToUpdate;
QSet<QPair<int, int>>::const_iterator it;
for (it = m_overflowCells.constBegin(); it != m_overflowCells.constEnd(); ++it) {
const QPair<int, int> &cellKey = *it;
indicesToUpdate.append(createIndex(cellKey.first, cellKey.second));
}
m_overflowCells.clear();
// Обновляем все затронутые ячейки
for (const QModelIndex &index : indicesToUpdate) {
emit dataChanged(index, index, QVector<int>() << Qt::BackgroundRole);
}
}
Qt::ItemFlags SpecificationPCBTableModel::flags(const QModelIndex &index) const
{
if (!index.isValid()) {
return Qt::NoItemFlags;
}
Qt::ItemFlags flags = Qt::ItemIsEnabled | Qt::ItemIsSelectable;
// Проверяем, можно ли редактировать эту строку
if (canEditRow(index.row())) {
flags |= Qt::ItemIsEditable;
}
return flags;
}
void SpecificationPCBTableModel::setColumnMappings(const QStringList &mappings)
{
if (m_columnMappings != mappings) {
m_columnMappings = mappings;
emit columnMappingsChanged();
emit modelDataChanged();
}
}
QStringList SpecificationPCBTableModel::columnMappings() const
{
return m_columnMappings;
}
void SpecificationPCBTableModel::addRow(const SpecificationPCBRowData &rowData)
{
beginInsertRows(QModelIndex(), m_rows.size(), m_rows.size());
m_rows.append(rowData);
endInsertRows();
// Автоматический пересчет отключен - пересчет выполняется по кнопке "Пересчет таблицы"
// updateRowNumbers();
// Оптимизируем разрывы страниц после добавления строки
// if (rowData.isHeader) {
// optimizePageBreaks();
// }
emit modelDataChanged();
}
void SpecificationPCBTableModel::insertRow(int position, const SpecificationPCBRowData &rowData)
{
if (position < 0 || position > m_rows.size()) {
return;
}
beginInsertRows(QModelIndex(), position, position);
m_rows.insert(position, rowData);
endInsertRows();
// Автоматический пересчет отключен - пересчет выполняется по кнопке "Пересчет таблицы"
// updateRowNumbers();
// Оптимизируем разрывы страниц после вставки строки
// if (rowData.isHeader) {
// optimizePageBreaks();
// }
emit modelDataChanged();
}
void SpecificationPCBTableModel::removeRow(int row)
{
if (row < 0 || row >= m_rows.size()) {
return;
}
// Проверяем, можно ли удалить эту строку
if (!canEditRow(row)) {
return;
}
beginRemoveRows(QModelIndex(), row, row);
m_rows.removeAt(row);
endRemoveRows();
// Автоматический пересчет отключен - пересчет выполняется по кнопке "Пересчет таблицы"
// updateRowNumbers();
// Оптимизируем разрывы страниц после удаления строки
// optimizePageBreaks();
emit modelDataChanged();
}
void SpecificationPCBTableModel::clear()
{
if (m_rows.isEmpty()) {
return;
}
beginResetModel();
m_rows.clear();
endResetModel();
emit modelDataChanged();
}
void SpecificationPCBTableModel::addEmptyRow()
{
addRow(createEmptyRow());
}
void SpecificationPCBTableModel::addEmptyRowAt(int position)
{
insertRow(position, createEmptyRow());
}
SpecificationPCBRowData SpecificationPCBTableModel::createEmptyRow() const
{
SpecificationPCBRowData row;
row.isHeader = false;
row.isEmpty = false; // Строка не пустая, так как может быть отредактирована
row.rowNumber = 0; // Будет обновлено в updateRowNumbers()
row.pageNumber = 1;
// Создаем пустые ячейки, но они могут быть отредактированы
row.format = SpecificationPCBCellData("", 0, 1, false, false, true);
row.zone = SpecificationPCBCellData("", 0, 1, false, false, true);
row.position = SpecificationPCBCellData("", 0, 1, false, false, true);
row.designation = SpecificationPCBCellData("", 0, 1, false, false, true);
row.name = SpecificationPCBCellData("", 0, 1, false, false, true);
row.quantity = SpecificationPCBCellData("", 0, 1, false, false, true);
row.note = SpecificationPCBCellData("", 0, 1, false, false, true);
return row;
}
bool SpecificationPCBTableModel::canEditRow(int row) const
{
if (row < 0 || row >= m_rows.size()) {
return false;
}
// Разрешаем редактирование и удаление всех строк, включая заголовки и автоматически сгенерированные
return true;
}
void SpecificationPCBTableModel::setComponents(const QList<ComponentModel*> &components)
{
m_components = components;
// Собираем доступные свойства из компонентов
m_propertyNames.clear();
m_propertyNames << "Designator" << "Name" << "Type" << "Value" << "Footprint" << "Description";
for (ComponentModel *component : components) {
QMap<QString, QString> props = component->properties();
for (auto it = props.begin(); it != props.end(); ++it) {
if (!m_propertyNames.contains(it.key())) {
m_propertyNames.append(it.key());
}
}
}
emit modelDataChanged();
}
void SpecificationPCBTableModel::setPropertyNames(const QStringList &propertyNames)
{
m_propertyNames = propertyNames;
}
QStringList SpecificationPCBTableModel::availableProperties() const
{
QStringList properties = m_propertyNames;
properties.prepend("<Пусто>");
properties.prepend("<Авто>");
return properties;
}
QByteArray SpecificationPCBTableModel::saveToDatabase() const
{
qDebug() << "SpecificationPCBTableModel::saveToDatabase: Начинаем сохранение в БД";
qDebug() << "SpecificationPCBTableModel::saveToDatabase: Количество строк:" << m_rows.size();
qDebug() << "SpecificationPCBTableModel::saveToDatabase: Настройки колонок:" << m_columnMappings;
QJsonObject root;
QJsonArray rowsArray;
for (const SpecificationPCBRowData &row : m_rows) {
QJsonObject rowObj;
// Сохраняем данные ячеек
QJsonObject formatObj;
formatObj["value"] = row.format.value;
formatObj["rowNumber"] = row.format.rowNumber;
formatObj["pageNumber"] = row.format.pageNumber;
formatObj["isHeader"] = row.format.isHeader;
formatObj["isAutoGenerated"] = row.format.isAutoGenerated;
formatObj["isEmpty"] = row.format.isEmpty;
formatObj["stretch"] = row.format.stretch;
formatObj["isUnderline"] = row.format.isUnderline;
rowObj["format"] = formatObj;
QJsonObject zoneObj;
zoneObj["value"] = row.zone.value;
zoneObj["rowNumber"] = row.zone.rowNumber;
zoneObj["pageNumber"] = row.zone.pageNumber;
zoneObj["isHeader"] = row.zone.isHeader;
zoneObj["isAutoGenerated"] = row.zone.isAutoGenerated;
zoneObj["isEmpty"] = row.zone.isEmpty;
zoneObj["stretch"] = row.zone.stretch;
zoneObj["isUnderline"] = row.zone.isUnderline;
rowObj["zone"] = zoneObj;
QJsonObject positionObj;
positionObj["value"] = row.position.value;
positionObj["rowNumber"] = row.position.rowNumber;
positionObj["pageNumber"] = row.position.pageNumber;
positionObj["isHeader"] = row.position.isHeader;
positionObj["isAutoGenerated"] = row.position.isAutoGenerated;
positionObj["isEmpty"] = row.position.isEmpty;
positionObj["stretch"] = row.position.stretch;
positionObj["isUnderline"] = row.position.isUnderline;
rowObj["position"] = positionObj;
QJsonObject designationObj;
designationObj["value"] = row.designation.value;
designationObj["rowNumber"] = row.designation.rowNumber;
designationObj["pageNumber"] = row.designation.pageNumber;
designationObj["isHeader"] = row.designation.isHeader;
designationObj["isAutoGenerated"] = row.designation.isAutoGenerated;
designationObj["isEmpty"] = row.designation.isEmpty;
designationObj["stretch"] = row.designation.stretch;
designationObj["isUnderline"] = row.designation.isUnderline;
rowObj["designation"] = designationObj;
QJsonObject nameObj;
nameObj["value"] = row.name.value;
nameObj["rowNumber"] = row.name.rowNumber;
nameObj["pageNumber"] = row.name.pageNumber;
nameObj["isHeader"] = row.name.isHeader;
nameObj["isAutoGenerated"] = row.name.isAutoGenerated;
nameObj["isEmpty"] = row.name.isEmpty;
nameObj["stretch"] = row.name.stretch;
nameObj["isUnderline"] = row.name.isUnderline;
rowObj["name"] = nameObj;
QJsonObject quantityObj;
quantityObj["value"] = row.quantity.value;
quantityObj["rowNumber"] = row.quantity.rowNumber;
quantityObj["pageNumber"] = row.quantity.pageNumber;
quantityObj["isHeader"] = row.quantity.isHeader;
quantityObj["isAutoGenerated"] = row.quantity.isAutoGenerated;
quantityObj["isEmpty"] = row.quantity.isEmpty;
quantityObj["stretch"] = row.quantity.stretch;
quantityObj["isUnderline"] = row.quantity.isUnderline;
rowObj["quantity"] = quantityObj;
QJsonObject noteObj;
noteObj["value"] = row.note.value;
noteObj["rowNumber"] = row.note.rowNumber;
noteObj["pageNumber"] = row.note.pageNumber;
noteObj["isHeader"] = row.note.isHeader;
noteObj["isAutoGenerated"] = row.note.isAutoGenerated;
noteObj["isEmpty"] = row.note.isEmpty;
noteObj["stretch"] = row.note.stretch;
noteObj["isUnderline"] = row.note.isUnderline;
rowObj["note"] = noteObj;
rowObj["isHeader"] = row.isHeader;
rowObj["isEmpty"] = row.isEmpty;
rowObj["rowNumber"] = row.rowNumber;
rowObj["pageNumber"] = row.pageNumber;
rowsArray.append(rowObj);
}
root["rows"] = rowsArray;
root["columnMappings"] = QJsonArray::fromStringList(m_columnMappings);
root["propertyNames"] = QJsonArray::fromStringList(m_propertyNames);
QJsonDocument doc(root);
QByteArray result = doc.toJson();
qDebug() << "SpecificationPCBTableModel::saveToDatabase: Сохранение завершено, размер данных:" << result.size() << "байт";
return result;
}
void SpecificationPCBTableModel::loadFromDatabase(const QByteArray &data)
{
qDebug() << "SpecificationPCBTableModel::loadFromDatabase: Начинаем загрузку из БД, размер данных:" << data.size();
QJsonDocument doc = QJsonDocument::fromJson(data);
if (!doc.isObject()) {
qDebug() << "SpecificationPCBTableModel::loadFromDatabase: Ошибка парсинга JSON";
return;
}
qDebug() << "SpecificationPCBTableModel::loadFromDatabase: JSON успешно распарсен";
QJsonObject root = doc.object();
// Загружаем маппинг колонок
if (root.contains("columnMappings")) {
QJsonArray mappingsArray = root["columnMappings"].toArray();
QStringList mappings;
for (const QJsonValue &value : mappingsArray) {
mappings.append(value.toString());
}
m_columnMappings = mappings;
}
// Загружаем свойства
if (root.contains("propertyNames")) {
QJsonArray propertiesArray = root["propertyNames"].toArray();
QStringList properties;
for (const QJsonValue &value : propertiesArray) {
properties.append(value.toString());
}
m_propertyNames = properties;
}
// Загружаем строки
clear();
if (root.contains("rows")) {
QJsonArray rowsArray = root["rows"].toArray();
for (const QJsonValue &value : rowsArray) {
QJsonObject rowObj = value.toObject();
SpecificationPCBRowData row;
// Загружаем данные ячеек
if (rowObj.contains("format")) {
QJsonObject formatObj = rowObj["format"].toObject();
int stretch = formatObj.contains("stretch") ? formatObj["stretch"].toInt(100) : 100;
bool isUnderline = formatObj.contains("isUnderline") ? formatObj["isUnderline"].toBool(false) : false;
row.format = SpecificationPCBCellData(
formatObj["value"].toString(),
formatObj["rowNumber"].toInt(),
formatObj["pageNumber"].toInt(),
formatObj["isHeader"].toBool(),
formatObj["isAutoGenerated"].toBool(),
formatObj["isEmpty"].toBool(),
stretch,
isUnderline
);
}
if (rowObj.contains("zone")) {
QJsonObject zoneObj = rowObj["zone"].toObject();
int stretch = zoneObj.contains("stretch") ? zoneObj["stretch"].toInt(100) : 100;
bool isUnderline = zoneObj.contains("isUnderline") ? zoneObj["isUnderline"].toBool(false) : false;
row.zone = SpecificationPCBCellData(
zoneObj["value"].toString(),
zoneObj["rowNumber"].toInt(),
zoneObj["pageNumber"].toInt(),
zoneObj["isHeader"].toBool(),
zoneObj["isAutoGenerated"].toBool(),
zoneObj["isEmpty"].toBool(),
stretch,
isUnderline
);
}
if (rowObj.contains("position")) {
QJsonObject positionObj = rowObj["position"].toObject();
int stretch = positionObj.contains("stretch") ? positionObj["stretch"].toInt(100) : 100;
bool isUnderline = positionObj.contains("isUnderline") ? positionObj["isUnderline"].toBool(false) : false;
row.position = SpecificationPCBCellData(
positionObj["value"].toString(),
positionObj["rowNumber"].toInt(),
positionObj["pageNumber"].toInt(),
positionObj["isHeader"].toBool(),
positionObj["isAutoGenerated"].toBool(),
positionObj["isEmpty"].toBool(),
stretch,
isUnderline
);
}
if (rowObj.contains("designation")) {
QJsonObject designationObj = rowObj["designation"].toObject();
int stretch = designationObj.contains("stretch") ? designationObj["stretch"].toInt(100) : 100;
bool isUnderline = designationObj.contains("isUnderline") ? designationObj["isUnderline"].toBool(false) : false;
row.designation = SpecificationPCBCellData(
designationObj["value"].toString(),
designationObj["rowNumber"].toInt(),
designationObj["pageNumber"].toInt(),
designationObj["isHeader"].toBool(),
designationObj["isAutoGenerated"].toBool(),
designationObj["isEmpty"].toBool(),
stretch,
isUnderline
);
}
if (rowObj.contains("name")) {
QJsonObject nameObj = rowObj["name"].toObject();
int stretch = nameObj.contains("stretch") ? nameObj["stretch"].toInt(100) : 100;
bool isUnderline = nameObj.contains("isUnderline") ? nameObj["isUnderline"].toBool(false) : false;
row.name = SpecificationPCBCellData(
nameObj["value"].toString(),
nameObj["rowNumber"].toInt(),
nameObj["pageNumber"].toInt(),
nameObj["isHeader"].toBool(),
nameObj["isAutoGenerated"].toBool(),
nameObj["isEmpty"].toBool(),
stretch,
isUnderline
);
}
if (rowObj.contains("quantity")) {
QJsonObject quantityObj = rowObj["quantity"].toObject();
int stretch = quantityObj.contains("stretch") ? quantityObj["stretch"].toInt(100) : 100;
bool isUnderline = quantityObj.contains("isUnderline") ? quantityObj["isUnderline"].toBool(false) : false;
row.quantity = SpecificationPCBCellData(
quantityObj["value"].toString(),
quantityObj["rowNumber"].toInt(),
quantityObj["pageNumber"].toInt(),
quantityObj["isHeader"].toBool(),
quantityObj["isAutoGenerated"].toBool(),
quantityObj["isEmpty"].toBool(),
stretch,
isUnderline
);
}
if (rowObj.contains("note")) {
QJsonObject noteObj = rowObj["note"].toObject();
int stretch = noteObj.contains("stretch") ? noteObj["stretch"].toInt(100) : 100;
bool isUnderline = noteObj.contains("isUnderline") ? noteObj["isUnderline"].toBool(false) : false;
row.note = SpecificationPCBCellData(
noteObj["value"].toString(),
noteObj["rowNumber"].toInt(),
noteObj["pageNumber"].toInt(),
noteObj["isHeader"].toBool(),
noteObj["isAutoGenerated"].toBool(),
noteObj["isEmpty"].toBool(),
stretch,
isUnderline
);
}
row.isHeader = rowObj["isHeader"].toBool();
row.isEmpty = rowObj["isEmpty"].toBool();
row.rowNumber = rowObj["rowNumber"].toInt();
row.pageNumber = rowObj["pageNumber"].toInt();
addRow(row);
}
}
qDebug() << "SpecificationPCBTableModel::loadFromDatabase: Загружено строк:" << m_rows.size();
qDebug() << "SpecificationPCBTableModel::loadFromDatabase: Маппинг колонок:" << m_columnMappings;
qDebug() << "SpecificationPCBTableModel::loadFromDatabase: Свойства:" << m_propertyNames;
emit columnMappingsChanged();
}
QString SpecificationPCBTableModel::getDocumentTitle() const
{
return "Спецификация печатной платы";
}
QString SpecificationPCBTableModel::getDocumentType() const
{
return "SpecificationPCB";
}
bool SpecificationPCBTableModel::isRowHeader(int row) const
{
if (row < 0 || row >= m_rows.size()) {
return false;
}
return m_rows[row].isHeader;
}
void SpecificationPCBTableModel::updatePageNumbers()
{
qDebug() << "SpecificationPCBTableModel::updatePageNumbers: Обновляем номера страниц";
int currentRow = 0;
int currentPage = 1;
int rowsOnCurrentPage = 0;
bool pageNumbersChanged = false;
for (int i = 0; i < m_rows.size(); ++i) {
SpecificationPCBRowData &row = m_rows[i];
// Сохраняем старый номер страницы для проверки изменений
int oldPageNumber = row.pageNumber;
// Определяем, сколько строк помещается на текущей странице
int maxRowsOnPage = (currentPage == 1) ? ROWS_PER_FIRST_PAGE : ROWS_PER_OTHER_PAGE;
// Если текущая страница заполнена, переходим на следующую
if (rowsOnCurrentPage >= maxRowsOnPage) {
currentPage++;
rowsOnCurrentPage = 0;
maxRowsOnPage = ROWS_PER_OTHER_PAGE; // Начиная со второй страницы
}
// Проверяем, изменился ли номер страницы
if (oldPageNumber != currentPage) {
pageNumbersChanged = true;
}
// Устанавливаем номер страницы для строки
row.pageNumber = currentPage;
row.format.pageNumber = currentPage;
row.zone.pageNumber = currentPage;
row.position.pageNumber = currentPage;
row.designation.pageNumber = currentPage;
row.name.pageNumber = currentPage;
row.quantity.pageNumber = currentPage;
row.note.pageNumber = currentPage;
// Увеличиваем счетчик строк на текущей странице
rowsOnCurrentPage++;
currentRow++;
qDebug() << "SpecificationPCBTableModel::updatePageNumbers: Строка" << i << "на странице" << currentPage
<< "(строк на странице:" << rowsOnCurrentPage << "/" << maxRowsOnPage << ")";
}
qDebug() << "SpecificationPCBTableModel::updatePageNumbers: Обновление завершено, всего страниц:" << currentPage;
// Уведомляем UI об изменении данных ТОЛЬКО если номера страниц действительно изменились
// Это критично для производительности при редактировании больших таблиц
if (pageNumbersChanged && !m_rows.isEmpty()) {
emit dataChanged(index(0, 0), index(m_rows.size() - 1, COLUMN_COUNT - 1),
QVector<int>() << Qt::BackgroundRole);
emit headerDataChanged(Qt::Vertical, 0, m_rows.size() - 1);
}
}
int SpecificationPCBTableModel::getPageForRow(int rowNumber) const
{
if (rowNumber <= 0) return 1;
int currentRow = 0;
int currentPage = 1;
for (int i = 0; i < m_rows.size(); ++i) {
int maxRowsOnPage = (currentPage == 1) ? ROWS_PER_FIRST_PAGE : ROWS_PER_OTHER_PAGE;
if (currentRow + 1 > maxRowsOnPage) {
currentPage++;
currentRow = 0;
maxRowsOnPage = ROWS_PER_OTHER_PAGE;
}
currentRow++;
if (i + 1 == rowNumber) {
return currentPage;
}
}
return currentPage;
}
bool SpecificationPCBTableModel::needsPageBreakProtection(int rowIndex) const
{
if (rowIndex < 0 || rowIndex >= m_rows.size()) {
return false;
}
const SpecificationPCBRowData &row = m_rows[rowIndex];
// Проверяем только заголовки групп
if (!row.isHeader) {
return false;
}
// Получаем номер страницы для текущей строки
int currentPage = row.pageNumber;
// Определяем максимальное количество строк на текущей странице
int maxRowsOnPage = (currentPage == 1) ? ROWS_PER_FIRST_PAGE : ROWS_PER_OTHER_PAGE;
// Подсчитываем количество строк на текущей странице до этой строки
int rowsOnCurrentPage = 0;
for (int i = 0; i < rowIndex; ++i) {
if (m_rows[i].pageNumber == currentPage) {
rowsOnCurrentPage++;
}
}
// Проверяем, находится ли заголовок на последней или предпоследней строке страницы
// Заголовки обособлены пустыми строками с двух сторон, поэтому:
// - Если заголовок на последней строке страницы - нужна защита
// - Если заголовок на предпоследней строке страницы - нужна защита (чтобы пустая строка после заголовка не оказалась на следующей странице)
if (rowsOnCurrentPage == maxRowsOnPage - 1 || rowsOnCurrentPage == maxRowsOnPage - 2) {
// Проверяем, есть ли элементы группы на следующей странице
for (int i = rowIndex + 1; i < m_rows.size(); ++i) {
// Если встретили другой заголовок, значит это уже другая группа
if (m_rows[i].isHeader) {
break;
}
// Если элемент группы находится на следующей странице
if (m_rows[i].pageNumber == currentPage + 1 && !m_rows[i].isEmpty) {
qDebug() << "SpecificationPCBTableModel::needsPageBreakProtection: Заголовок на строке" << rowIndex
<< "нуждается в защите от разрыва страницы (элемент группы на странице" << (currentPage + 1)
<< ", заголовок на строке" << (rowsOnCurrentPage + 1) << "из" << maxRowsOnPage << ")";
return true;
}
}
}
return false;
}
void SpecificationPCBTableModel::addPageBreakProtection()
{
qDebug() << "SpecificationPCBTableModel::addPageBreakProtection: Добавляем защиту от разрывов страниц";
// Сначала обновляем номера страниц
updatePageNumbers();
// Собираем информацию о заголовках, которые нуждаются в защите
QList<PageBreakInfo> pageBreakInfos;
for (int i = 0; i < m_rows.size(); ++i) {
if (needsPageBreakProtection(i)) {
const SpecificationPCBRowData &row = m_rows[i];
int currentPage = row.pageNumber;
int maxRowsOnPage = (currentPage == 1) ? ROWS_PER_FIRST_PAGE : ROWS_PER_OTHER_PAGE;
// Подсчитываем количество строк на текущей странице до этой строки
int rowsOnCurrentPage = 0;
for (int j = 0; j < i; ++j) {
if (m_rows[j].pageNumber == currentPage) {
rowsOnCurrentPage++;
}
}
// Определяем, сколько пустых строк нужно добавить
int emptyRowsToAdd = 1; // По умолчанию одна строка
if (rowsOnCurrentPage == maxRowsOnPage - 2) {
// Заголовок на предпоследней строке - добавляем две пустые строки
emptyRowsToAdd = 2;
qDebug() << "SpecificationPCBTableModel::addPageBreakProtection: Заголовок на предпоследней строке, добавляем" << emptyRowsToAdd << "пустые строки перед строкой" << i;
} else {
qDebug() << "SpecificationPCBTableModel::addPageBreakProtection: Заголовок на последней строке, добавляем" << emptyRowsToAdd << "пустую строку перед строкой" << i;
}
// Создаем информацию о переносе
PageBreakInfo info;
info.rowIndex = i;
info.headerText = row.name.value;
info.currentPage = currentPage;
info.nextPage = currentPage + 1;
info.emptyRowsToAdd = emptyRowsToAdd;
pageBreakInfos.append(info);
}
}
// Если есть заголовки для защиты, запрашиваем подтверждение
if (!pageBreakInfos.isEmpty()) {
qDebug() << "SpecificationPCBTableModel::addPageBreakProtection: Найдено" << pageBreakInfos.size() << "заголовков для защиты";
// Находим главное окно приложения
QWidget *mainWindow = nullptr;
for (QWidget *widget : QApplication::topLevelWidgets()) {
if (widget->isVisible() && widget->windowTitle().contains("GostGenerator", Qt::CaseInsensitive)) {
mainWindow = widget;
break;
}
}
// Создаем и показываем диалог подтверждения
PageBreakConfirmationDialog dialog(pageBreakInfos, mainWindow);
if (dialog.exec() == QDialog::Accepted) {
// Получаем подтвержденные строки
QList<int> confirmedRows = dialog.getConfirmedRows();
// Добавляем пустые строки только для подтвержденных заголовков
for (int i = 0; i < m_rows.size(); ++i) {
if (needsPageBreakProtection(i) && confirmedRows.contains(i)) {
const SpecificationPCBRowData &row = m_rows[i];
int currentPage = row.pageNumber;
int maxRowsOnPage = (currentPage == 1) ? ROWS_PER_FIRST_PAGE : ROWS_PER_OTHER_PAGE;
// Подсчитываем количество строк на текущей странице до этой строки
int rowsOnCurrentPage = 0;
for (int j = 0; j < i; ++j) {
if (m_rows[j].pageNumber == currentPage) {
rowsOnCurrentPage++;
}
}
// Определяем, сколько пустых строк нужно добавить
int emptyRowsToAdd = 1; // По умолчанию одна строка
if (rowsOnCurrentPage == maxRowsOnPage - 2) {
// Заголовок на предпоследней строке - добавляем две пустые строки
emptyRowsToAdd = 2;
qDebug() << "SpecificationPCBTableModel::addPageBreakProtection: Добавляем" << emptyRowsToAdd << "пустые строки перед строкой" << i;
} else {
qDebug() << "SpecificationPCBTableModel::addPageBreakProtection: Добавляем" << emptyRowsToAdd << "пустую строку перед строкой" << i;
}
// Добавляем нужное количество пустых строк перед заголовком
for (int k = 0; k < emptyRowsToAdd; ++k) {
SpecificationPCBRowData emptyRow = createEmptyRow();
insertRow(i, emptyRow);
}
// Обновляем номера страниц после вставки
updatePageNumbers();
// Пропускаем вставленные строки
i += emptyRowsToAdd;
}
}
} else {
qDebug() << "SpecificationPCBTableModel::addPageBreakProtection: Пользователь отменил добавление защиты от разрывов страниц";
}
}
// Обновляем отображение страниц в UI
updatePageDisplay();
qDebug() << "SpecificationPCBTableModel::addPageBreakProtection: Защита от разрывов страниц добавлена";
}
void SpecificationPCBTableModel::optimizePageBreaks()
{
updatePageNumbers();
addPageBreakProtection();
}
void SpecificationPCBTableModel::updatePageDisplay()
{
qDebug() << "SpecificationPCBTableModel::updatePageDisplay: Обновляем отображение страниц в UI";
// Сохраняем старые номера страниц для проверки изменений
QList<int> oldPageNumbers;
for (const SpecificationPCBRowData &row : m_rows) {
oldPageNumbers.append(row.pageNumber);
}
// Обновляем номера страниц
updatePageNumbers();
// Проверяем, изменились ли номера страниц
bool pageNumbersChanged = false;
for (int i = 0; i < m_rows.size() && i < oldPageNumbers.size(); ++i) {
if (m_rows[i].pageNumber != oldPageNumbers[i]) {
pageNumbersChanged = true;
break;
}
}
// Уведомляем UI об изменении данных ТОЛЬКО если номера страниц действительно изменились
// Это критично для производительности при редактировании больших таблиц
if (pageNumbersChanged && !m_rows.isEmpty()) {
emit dataChanged(index(0, 0), index(m_rows.size() - 1, COLUMN_COUNT - 1),
QVector<int>() << Qt::BackgroundRole);
// Обновляем вертикальные заголовки
emit headerDataChanged(Qt::Vertical, 0, m_rows.size() - 1);
}
qDebug() << "SpecificationPCBTableModel::updatePageDisplay: Отображение страниц обновлено";
}
void SpecificationPCBTableModel::updateRowNumbers()
{
int rowNumber = 1;
for (SpecificationPCBRowData &row : m_rows) {
if (!row.isHeader) {
row.rowNumber = rowNumber++;
}
}
}
void SpecificationPCBTableModel::updateRowEmptyStatus(int row)
{
if (row < 0 || row >= m_rows.size()) {
return;
}
SpecificationPCBRowData &rowData = m_rows[row];
// Проверяем, пустые ли все ячейки
bool allEmpty = rowData.format.value.isEmpty() &&
rowData.zone.value.isEmpty() &&
rowData.position.value.isEmpty() &&
rowData.designation.value.isEmpty() &&
rowData.name.value.isEmpty() &&
rowData.quantity.value.isEmpty() &&
rowData.note.value.isEmpty();
// Обновляем флаг isEmpty для строки
if (rowData.isEmpty != allEmpty) {
rowData.isEmpty = allEmpty;
qDebug() << "SpecificationPCBTableModel::updateRowEmptyStatus: Строка" << row << "обновлена, isEmpty =" << allEmpty;
}
}
SpecificationPCBCellData SpecificationPCBTableModel::createCellData(const QString &value, bool isHeader, bool isAuto, bool isEmpty)
{
return SpecificationPCBCellData(value, 0, 1, isHeader, isAuto, isEmpty);
}