1307 lines
50 KiB
C++
1307 lines
50 KiB
C++
#include "specificationtablemodel.h"
|
|
#include "pcbmaterialmodel.h"
|
|
#include <QDebug>
|
|
#include <QJsonDocument>
|
|
#include <QJsonObject>
|
|
#include <QJsonArray>
|
|
#include <QColor>
|
|
#include <QBrush>
|
|
#include <QFont>
|
|
#include <QFontMetrics>
|
|
#include <QApplication>
|
|
#include <QSet>
|
|
#include <QPair>
|
|
|
|
// Регистрируем структуры для работы с QVariant
|
|
Q_DECLARE_METATYPE(SpecificationCellData)
|
|
Q_DECLARE_METATYPE(SpecificationRowData)
|
|
|
|
// Локальная структура для информации о переносе заголовков
|
|
struct PageBreakInfo {
|
|
int rowIndex;
|
|
QString headerText;
|
|
int currentPage;
|
|
int nextPage;
|
|
int emptyRowsToAdd;
|
|
};
|
|
|
|
// Статические константы
|
|
const QStringList SpecificationTableModel::DEFAULT_HEADERS = QStringList()
|
|
<< "Формат" << "Зона" << "Позиция" << "Обозначение" << "Наименование" << "Кол." << "Примечание";
|
|
|
|
SpecificationTableModel::SpecificationTableModel(QObject *parent)
|
|
: QAbstractTableModel(parent)
|
|
{
|
|
// Устанавливаем маппинг по умолчанию
|
|
m_columnMappings << "Format" << "Zone" << "Position" << "Designation" << "Name" << "Quantity" << "Note";
|
|
}
|
|
|
|
int SpecificationTableModel::rowCount(const QModelIndex &parent) const
|
|
{
|
|
if (parent.isValid())
|
|
return 0;
|
|
return m_rows.size();
|
|
}
|
|
|
|
int SpecificationTableModel::columnCount(const QModelIndex &parent) const
|
|
{
|
|
if (parent.isValid())
|
|
return 0;
|
|
return COLUMN_COUNT;
|
|
}
|
|
|
|
QVariant SpecificationTableModel::data(const QModelIndex &index, int role) const
|
|
{
|
|
if (!index.isValid() || index.row() >= m_rows.size())
|
|
return QVariant();
|
|
|
|
const SpecificationRowData &row = m_rows[index.row()];
|
|
|
|
if (role == Qt::DisplayRole || role == Qt::EditRole) {
|
|
switch (index.column()) {
|
|
case 0: return row.format.value;
|
|
case 1: return row.zone.value;
|
|
case 2: return row.position.value;
|
|
case 3: return row.designation.value;
|
|
case 4: return row.name.value;
|
|
case 5: return row.quantity.value;
|
|
case 6: return row.note.value;
|
|
default: return QVariant();
|
|
}
|
|
} else if (role == Qt::BackgroundRole) {
|
|
if (row.isHeader) {
|
|
return QBrush(QColor(200, 200, 200));
|
|
} else {
|
|
// Проверяем переполнение ячейки (используем приблизительную ширину колонки)
|
|
// Приблизительные ширины колонок для Specification (в пикселях)
|
|
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 (row.pageNumber % 2 == 1) {
|
|
return QBrush(QColor(240, 248, 255)); // AliceBlue - для нечетных страниц
|
|
} else {
|
|
return QBrush(QColor(255, 255, 240)); // Ivory - для четных страниц
|
|
}
|
|
}
|
|
} else if (role == Qt::FontRole) {
|
|
QFont font;
|
|
bool isBold = false;
|
|
bool isUnderline = false;
|
|
|
|
// Проверяем свойства ячейки
|
|
switch (index.column()) {
|
|
case 0:
|
|
isBold = row.format.isHeader;
|
|
isUnderline = row.format.isUnderline;
|
|
break;
|
|
case 1:
|
|
isBold = row.zone.isHeader;
|
|
isUnderline = row.zone.isUnderline;
|
|
break;
|
|
case 2:
|
|
isBold = row.position.isHeader;
|
|
isUnderline = row.position.isUnderline;
|
|
break;
|
|
case 3:
|
|
isBold = row.designation.isHeader;
|
|
isUnderline = row.designation.isUnderline;
|
|
break;
|
|
case 4:
|
|
isBold = row.name.isHeader;
|
|
isUnderline = row.name.isUnderline;
|
|
break;
|
|
case 5:
|
|
isBold = row.quantity.isHeader;
|
|
isUnderline = row.quantity.isUnderline;
|
|
break;
|
|
case 6:
|
|
isBold = row.note.isHeader;
|
|
isUnderline = row.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(row.format);
|
|
case 1: return QVariant::fromValue(row.zone);
|
|
case 2: return QVariant::fromValue(row.position);
|
|
case 3: return QVariant::fromValue(row.designation);
|
|
case 4: return QVariant::fromValue(row.name);
|
|
case 5: return QVariant::fromValue(row.quantity);
|
|
case 6: return QVariant::fromValue(row.note);
|
|
default: return QVariant();
|
|
}
|
|
}
|
|
|
|
return QVariant();
|
|
}
|
|
|
|
QVariant SpecificationTableModel::headerData(int section, Qt::Orientation orientation, int role) const
|
|
{
|
|
if (role != Qt::DisplayRole)
|
|
return QVariant();
|
|
|
|
if (orientation == Qt::Horizontal) {
|
|
if (section < DEFAULT_HEADERS.size()) {
|
|
return DEFAULT_HEADERS[section];
|
|
}
|
|
} else {
|
|
// Вертикальные заголовки - номера строк с указанием страницы
|
|
if (section < m_rows.size()) {
|
|
const SpecificationRowData &row = m_rows[section];
|
|
return QString("%1 (стр. %2)").arg(section + 1).arg(row.pageNumber);
|
|
} else {
|
|
return section + 1;
|
|
}
|
|
}
|
|
|
|
return QVariant();
|
|
}
|
|
|
|
bool SpecificationTableModel::setData(const QModelIndex &index, const QVariant &value, int role)
|
|
{
|
|
if (!index.isValid() || index.row() >= m_rows.size() || role != Qt::EditRole)
|
|
return false;
|
|
|
|
// Проверяем, можно ли редактировать эту строку
|
|
if (!canEditRow(index.row())) {
|
|
qDebug() << "SpecificationTableModel::setData: Строка" << index.row() << "не может быть отредактирована";
|
|
return false;
|
|
}
|
|
|
|
SpecificationRowData &row = m_rows[index.row()];
|
|
QString newValue = value.toString();
|
|
|
|
switch (index.column()) {
|
|
case 0:
|
|
row.format.value = newValue;
|
|
row.format.isAutoGenerated = false;
|
|
row.format.isEmpty = newValue.isEmpty();
|
|
break;
|
|
case 1:
|
|
row.zone.value = newValue;
|
|
row.zone.isAutoGenerated = false;
|
|
row.zone.isEmpty = newValue.isEmpty();
|
|
break;
|
|
case 2:
|
|
row.position.value = newValue;
|
|
row.position.isAutoGenerated = false;
|
|
row.position.isEmpty = newValue.isEmpty();
|
|
break;
|
|
case 3:
|
|
row.designation.value = newValue;
|
|
row.designation.isAutoGenerated = false;
|
|
row.designation.isEmpty = newValue.isEmpty();
|
|
break;
|
|
case 4:
|
|
row.name.value = newValue;
|
|
row.name.isAutoGenerated = false;
|
|
row.name.isEmpty = newValue.isEmpty();
|
|
break;
|
|
case 5:
|
|
row.quantity.value = newValue;
|
|
row.quantity.isAutoGenerated = false;
|
|
row.quantity.isEmpty = newValue.isEmpty();
|
|
break;
|
|
case 6:
|
|
row.note.value = newValue;
|
|
row.note.isAutoGenerated = false;
|
|
row.note.isEmpty = newValue.isEmpty();
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
// Обновляем флаг isEmpty для строки, если все ячейки пустые
|
|
updateRowEmptyStatus(index.row());
|
|
|
|
emit dataChanged(index, index, QVector<int>() << role);
|
|
return true;
|
|
}
|
|
|
|
bool SpecificationTableModel::setCellStretch(int row, int column, int stretch)
|
|
{
|
|
if (row < 0 || row >= m_rows.size() || column < 0 || column >= COLUMN_COUNT) {
|
|
return false;
|
|
}
|
|
|
|
SpecificationRowData &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 SpecificationTableModel::setCellHeader(int row, int column, bool isHeader)
|
|
{
|
|
if (row < 0 || row >= m_rows.size() || column < 0 || column >= COLUMN_COUNT) {
|
|
return false;
|
|
}
|
|
|
|
SpecificationRowData &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 SpecificationTableModel::setCellUnderline(int row, int column, bool isUnderline)
|
|
{
|
|
if (row < 0 || row >= m_rows.size() || column < 0 || column >= COLUMN_COUNT) {
|
|
return false;
|
|
}
|
|
|
|
SpecificationRowData &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 SpecificationTableModel::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 SpecificationRowData &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);
|
|
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 SpecificationTableModel::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 SpecificationTableModel::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 SpecificationTableModel::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 SpecificationTableModel::setColumnMappings(const QStringList &mappings)
|
|
{
|
|
if (m_columnMappings != mappings) {
|
|
m_columnMappings = mappings;
|
|
emit columnMappingsChanged();
|
|
}
|
|
}
|
|
|
|
QStringList SpecificationTableModel::columnMappings() const
|
|
{
|
|
return m_columnMappings;
|
|
}
|
|
|
|
void SpecificationTableModel::addRow(const SpecificationRowData &rowData)
|
|
{
|
|
beginInsertRows(QModelIndex(), m_rows.size(), m_rows.size());
|
|
m_rows.append(rowData);
|
|
endInsertRows();
|
|
// Автоматический пересчет отключен - пересчет выполняется по кнопке "Пересчет таблицы"
|
|
// updateRowNumbers();
|
|
|
|
// Оптимизируем разрывы страниц после добавления строки
|
|
// if (rowData.isHeader) {
|
|
// optimizePageBreaks();
|
|
// }
|
|
|
|
emit modelDataChanged();
|
|
}
|
|
|
|
void SpecificationTableModel::insertRow(int position, const SpecificationRowData &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 SpecificationTableModel::removeRow(int row)
|
|
{
|
|
if (row < 0 || row >= m_rows.size())
|
|
return;
|
|
|
|
beginRemoveRows(QModelIndex(), row, row);
|
|
m_rows.removeAt(row);
|
|
endRemoveRows();
|
|
// Автоматический пересчет отключен - пересчет выполняется по кнопке "Пересчет таблицы"
|
|
// updateRowNumbers();
|
|
|
|
// Оптимизируем разрывы страниц после удаления строки
|
|
// optimizePageBreaks();
|
|
|
|
emit modelDataChanged();
|
|
}
|
|
|
|
void SpecificationTableModel::clear()
|
|
{
|
|
qDebug() << "SpecificationTableModel::clear: Очищаем таблицу, строк:" << m_rows.size();
|
|
|
|
if (m_rows.isEmpty()) {
|
|
qDebug() << "SpecificationTableModel::clear: Таблица уже пуста";
|
|
return;
|
|
}
|
|
|
|
beginResetModel();
|
|
m_rows.clear();
|
|
endResetModel();
|
|
|
|
qDebug() << "SpecificationTableModel::clear: Таблица очищена";
|
|
emit modelDataChanged();
|
|
}
|
|
|
|
void SpecificationTableModel::setMaterials(const QList<PCBMaterialModel*> &materials)
|
|
{
|
|
qDebug() << "SpecificationTableModel::setMaterials: Получено" << materials.size() << "материалов";
|
|
|
|
// Проверяем материалы на null
|
|
QList<PCBMaterialModel*> validMaterials;
|
|
for (PCBMaterialModel *material : materials) {
|
|
if (material) {
|
|
validMaterials.append(material);
|
|
} else {
|
|
qDebug() << "SpecificationTableModel::setMaterials: Пропускаем null материал";
|
|
}
|
|
}
|
|
|
|
// Сохраняем только валидные материалы
|
|
m_materials = validMaterials;
|
|
|
|
qDebug() << "SpecificationTableModel::setMaterials: Сохранено" << m_materials.size() << "валидных материалов";
|
|
}
|
|
|
|
void SpecificationTableModel::setPropertyNames(const QStringList &propertyNames)
|
|
{
|
|
qDebug() << "SpecificationTableModel::setPropertyNames: получено" << propertyNames;
|
|
m_propertyNames = propertyNames;
|
|
|
|
// Обновляем маппинг по умолчанию, если есть основные свойства
|
|
if (m_columnMappings.isEmpty()) {
|
|
m_columnMappings.clear();
|
|
m_columnMappings << "Format" << "Zone" << "Position" << "Designation" << "Name" << "Quantity" << "Note";
|
|
emit columnMappingsChanged();
|
|
}
|
|
}
|
|
|
|
QStringList SpecificationTableModel::availableProperties() const
|
|
{
|
|
QStringList properties = m_propertyNames;
|
|
properties.prepend("<Пусто>");
|
|
properties.prepend("<Авто>");
|
|
qDebug() << "SpecificationTableModel::availableProperties: m_propertyNames =" << m_propertyNames;
|
|
qDebug() << "SpecificationTableModel::availableProperties: возвращаем" << properties;
|
|
return properties;
|
|
}
|
|
|
|
QByteArray SpecificationTableModel::saveToDatabase() const
|
|
{
|
|
qDebug() << "SpecificationTableModel::saveToDatabase: Начинаем сохранение в БД";
|
|
qDebug() << "SpecificationTableModel::saveToDatabase: Количество строк:" << m_rows.size();
|
|
qDebug() << "SpecificationTableModel::saveToDatabase: Настройки колонок:" << m_columnMappings;
|
|
|
|
QJsonObject root;
|
|
QJsonArray rowsArray;
|
|
|
|
for (const SpecificationRowData &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() << "SpecificationTableModel::saveToDatabase: Сохранение завершено, размер данных:" << result.size() << "байт";
|
|
return result;
|
|
}
|
|
|
|
void SpecificationTableModel::loadFromDatabase(const QByteArray &data)
|
|
{
|
|
qDebug() << "SpecificationTableModel::loadFromDatabase: Начинаем загрузку из БД, размер данных:" << data.size();
|
|
|
|
QJsonDocument doc = QJsonDocument::fromJson(data);
|
|
if (!doc.isObject()) {
|
|
qDebug() << "SpecificationTableModel::loadFromDatabase: Ошибка парсинга JSON";
|
|
return;
|
|
}
|
|
|
|
qDebug() << "SpecificationTableModel::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();
|
|
SpecificationRowData row;
|
|
|
|
// Загружаем данные ячеек
|
|
if (rowObj.contains("format")) {
|
|
QJsonObject formatObj = rowObj["format"].toObject();
|
|
int stretch = formatObj["stretch"].toInt(100);
|
|
bool isUnderline = formatObj["isUnderline"].toBool(false);
|
|
row.format = SpecificationCellData(
|
|
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["stretch"].toInt(100);
|
|
bool isUnderline = zoneObj["isUnderline"].toBool(false);
|
|
row.zone = SpecificationCellData(
|
|
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 posObj = rowObj["position"].toObject();
|
|
int stretch = posObj["stretch"].toInt(100);
|
|
bool isUnderline = posObj["isUnderline"].toBool(false);
|
|
row.position = SpecificationCellData(
|
|
posObj["value"].toString(),
|
|
posObj["rowNumber"].toInt(),
|
|
posObj["pageNumber"].toInt(),
|
|
posObj["isHeader"].toBool(),
|
|
posObj["isAutoGenerated"].toBool(),
|
|
posObj["isEmpty"].toBool(),
|
|
stretch,
|
|
isUnderline
|
|
);
|
|
}
|
|
|
|
if (rowObj.contains("designation")) {
|
|
QJsonObject desObj = rowObj["designation"].toObject();
|
|
int stretch = desObj["stretch"].toInt(100);
|
|
bool isUnderline = desObj["isUnderline"].toBool(false);
|
|
row.designation = SpecificationCellData(
|
|
desObj["value"].toString(),
|
|
desObj["rowNumber"].toInt(),
|
|
desObj["pageNumber"].toInt(),
|
|
desObj["isHeader"].toBool(),
|
|
desObj["isAutoGenerated"].toBool(),
|
|
desObj["isEmpty"].toBool(),
|
|
stretch,
|
|
isUnderline
|
|
);
|
|
}
|
|
|
|
if (rowObj.contains("name")) {
|
|
QJsonObject nameObj = rowObj["name"].toObject();
|
|
int stretch = nameObj["stretch"].toInt(100);
|
|
bool isUnderline = nameObj["isUnderline"].toBool(false);
|
|
row.name = SpecificationCellData(
|
|
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 qtyObj = rowObj["quantity"].toObject();
|
|
int stretch = qtyObj["stretch"].toInt(100);
|
|
bool isUnderline = qtyObj["isUnderline"].toBool(false);
|
|
row.quantity = SpecificationCellData(
|
|
qtyObj["value"].toString(),
|
|
qtyObj["rowNumber"].toInt(),
|
|
qtyObj["pageNumber"].toInt(),
|
|
qtyObj["isHeader"].toBool(),
|
|
qtyObj["isAutoGenerated"].toBool(),
|
|
qtyObj["isEmpty"].toBool(),
|
|
stretch,
|
|
isUnderline
|
|
);
|
|
}
|
|
|
|
if (rowObj.contains("note")) {
|
|
QJsonObject noteObj = rowObj["note"].toObject();
|
|
int stretch = noteObj["stretch"].toInt(100);
|
|
bool isUnderline = noteObj["isUnderline"].toBool(false);
|
|
row.note = SpecificationCellData(
|
|
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() << "SpecificationTableModel::loadFromDatabase: Загружено строк:" << m_rows.size();
|
|
qDebug() << "SpecificationTableModel::loadFromDatabase: Маппинг колонок:" << m_columnMappings;
|
|
qDebug() << "SpecificationTableModel::loadFromDatabase: Свойства:" << m_propertyNames;
|
|
|
|
emit columnMappingsChanged();
|
|
}
|
|
|
|
QString SpecificationTableModel::getDocumentTitle() const
|
|
{
|
|
return "Спецификация";
|
|
}
|
|
|
|
QString SpecificationTableModel::getDocumentType() const
|
|
{
|
|
return "Specification";
|
|
}
|
|
|
|
void SpecificationTableModel::updateRowNumbers()
|
|
{
|
|
int rowNumber = 1;
|
|
for (SpecificationRowData &row : m_rows) {
|
|
if (!row.isHeader) {
|
|
row.rowNumber = rowNumber++;
|
|
}
|
|
}
|
|
}
|
|
|
|
void SpecificationTableModel::updatePageNumbers()
|
|
{
|
|
qDebug() << "SpecificationTableModel::updatePageNumbers: Обновляем номера страниц";
|
|
|
|
int currentRow = 0;
|
|
int currentPage = 1;
|
|
int rowsOnCurrentPage = 0;
|
|
|
|
for (int i = 0; i < m_rows.size(); ++i) {
|
|
SpecificationRowData &row = m_rows[i];
|
|
|
|
// Определяем, сколько строк помещается на текущей странице
|
|
int maxRowsOnPage = (currentPage == 1) ? ROWS_PER_FIRST_PAGE : ROWS_PER_OTHER_PAGE;
|
|
|
|
// Если текущая страница заполнена, переходим на следующую
|
|
if (rowsOnCurrentPage >= maxRowsOnPage) {
|
|
currentPage++;
|
|
rowsOnCurrentPage = 0;
|
|
maxRowsOnPage = ROWS_PER_OTHER_PAGE; // Начиная со второй страницы
|
|
}
|
|
|
|
// Устанавливаем номер страницы для строки
|
|
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() << "SpecificationTableModel::updatePageNumbers: Строка" << i << "на странице" << currentPage
|
|
<< "(строк на странице:" << rowsOnCurrentPage << "/" << maxRowsOnPage << ")";
|
|
}
|
|
|
|
qDebug() << "SpecificationTableModel::updatePageNumbers: Обновление завершено, всего страниц:" << currentPage;
|
|
|
|
// Уведомляем UI об изменении данных для обновления отображения страниц
|
|
if (!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 SpecificationTableModel::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 SpecificationTableModel::needsPageBreakProtection(int rowIndex) const
|
|
{
|
|
if (rowIndex < 0 || rowIndex >= m_rows.size()) {
|
|
return false;
|
|
}
|
|
|
|
const SpecificationRowData &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() << "SpecificationTableModel::needsPageBreakProtection: Заголовок на строке" << rowIndex
|
|
<< "нуждается в защите от разрыва страницы (элемент группы на странице" << (currentPage + 1)
|
|
<< ", заголовок на строке" << (rowsOnCurrentPage + 1) << "из" << maxRowsOnPage << ")";
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void SpecificationTableModel::addPageBreakProtection()
|
|
{
|
|
qDebug() << "SpecificationTableModel::addPageBreakProtection: Добавляем защиту от разрывов страниц";
|
|
|
|
// Сначала обновляем номера страниц
|
|
updatePageNumbers();
|
|
|
|
// Собираем информацию о заголовках, которые нуждаются в защите
|
|
QList<PageBreakInfo> pageBreakInfos;
|
|
|
|
for (int i = 0; i < m_rows.size(); ++i) {
|
|
if (needsPageBreakProtection(i)) {
|
|
const SpecificationRowData &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() << "SpecificationTableModel::addPageBreakProtection: Заголовок на предпоследней строке, добавляем" << emptyRowsToAdd << "пустые строки перед строкой" << i;
|
|
} else {
|
|
qDebug() << "SpecificationTableModel::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() << "SpecificationTableModel::addPageBreakProtection: Найдено" << pageBreakInfos.size() << "заголовков для защиты, автоматически добавляем пустые строки";
|
|
|
|
// Добавляем пустые строки для всех заголовков, нуждающихся в защите
|
|
for (int i = 0; i < m_rows.size(); ++i) {
|
|
if (needsPageBreakProtection(i)) {
|
|
const SpecificationRowData &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() << "SpecificationTableModel::addPageBreakProtection: Добавляем" << emptyRowsToAdd << "пустые строки перед строкой" << i;
|
|
} else {
|
|
qDebug() << "SpecificationTableModel::addPageBreakProtection: Добавляем" << emptyRowsToAdd << "пустую строку перед строкой" << i;
|
|
}
|
|
|
|
// Добавляем нужное количество пустых строк перед заголовком
|
|
for (int k = 0; k < emptyRowsToAdd; ++k) {
|
|
SpecificationRowData emptyRow = createEmptyRow();
|
|
insertRow(i, emptyRow);
|
|
}
|
|
|
|
// Обновляем номера страниц после вставки
|
|
updatePageNumbers();
|
|
|
|
// Пропускаем вставленные строки
|
|
i += emptyRowsToAdd;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Обновляем отображение страниц в UI
|
|
updatePageDisplay();
|
|
|
|
qDebug() << "SpecificationTableModel::addPageBreakProtection: Защита от разрывов страниц добавлена";
|
|
}
|
|
|
|
void SpecificationTableModel::optimizePageBreaks()
|
|
{
|
|
qDebug() << "SpecificationTableModel::optimizePageBreaks: Оптимизируем разрывы страниц";
|
|
|
|
// Добавляем защиту от разрывов страниц
|
|
addPageBreakProtection();
|
|
|
|
qDebug() << "SpecificationTableModel::optimizePageBreaks: Оптимизация завершена";
|
|
}
|
|
|
|
void SpecificationTableModel::updatePageDisplay()
|
|
{
|
|
qDebug() << "SpecificationTableModel::updatePageDisplay: Обновляем отображение страниц в UI";
|
|
|
|
// Обновляем номера страниц
|
|
updatePageNumbers();
|
|
|
|
// Уведомляем UI об изменении всех данных для обновления отображения
|
|
if (!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() << "SpecificationTableModel::updatePageDisplay: Отображение страниц обновлено";
|
|
}
|
|
|
|
SpecificationCellData SpecificationTableModel::createCellData(const QString &value, bool isHeader,
|
|
bool isAuto, bool isEmpty)
|
|
{
|
|
return SpecificationCellData(value, 0, 1, isHeader, isAuto, isEmpty);
|
|
}
|
|
|
|
// Новые методы для управления строками
|
|
void SpecificationTableModel::addEmptyRow()
|
|
{
|
|
SpecificationRowData emptyRow = createEmptyRow();
|
|
addRow(emptyRow);
|
|
}
|
|
|
|
void SpecificationTableModel::addEmptyRowAt(int position)
|
|
{
|
|
SpecificationRowData emptyRow = createEmptyRow();
|
|
insertRow(position, emptyRow);
|
|
}
|
|
|
|
SpecificationRowData SpecificationTableModel::createEmptyRow() const
|
|
{
|
|
SpecificationRowData row;
|
|
row.isHeader = false;
|
|
row.isEmpty = false; // Строка не пустая, так как может быть отредактирована
|
|
row.rowNumber = 0; // Будет обновлено в updateRowNumbers()
|
|
row.pageNumber = 1;
|
|
|
|
// Создаем пустые ячейки, но они могут быть отредактированы
|
|
row.format = SpecificationCellData("", 0, 1, false, false, true);
|
|
row.zone = SpecificationCellData("", 0, 1, false, false, true);
|
|
row.position = SpecificationCellData("", 0, 1, false, false, true);
|
|
row.designation = SpecificationCellData("", 0, 1, false, false, true);
|
|
row.name = SpecificationCellData("", 0, 1, false, false, true);
|
|
row.quantity = SpecificationCellData("", 0, 1, false, false, true);
|
|
row.note = SpecificationCellData("", 0, 1, false, false, true);
|
|
|
|
return row;
|
|
}
|
|
|
|
bool SpecificationTableModel::canEditRow(int row) const
|
|
{
|
|
if (row < 0 || row >= m_rows.size())
|
|
return false;
|
|
|
|
// Разрешаем редактирование и удаление всех строк, включая заголовки и пустые
|
|
return true;
|
|
}
|
|
|
|
void SpecificationTableModel::updateRowEmptyStatus(int row)
|
|
{
|
|
if (row < 0 || row >= m_rows.size())
|
|
return;
|
|
|
|
SpecificationRowData &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() << "SpecificationTableModel::updateRowEmptyStatus: Строка" << row << "обновлена, isEmpty =" << allEmpty;
|
|
}
|
|
}
|
|
|
|
bool SpecificationTableModel::isRowHeader(int row) const
|
|
{
|
|
if (row < 0 || row >= m_rows.size())
|
|
return false;
|
|
|
|
return m_rows[row].isHeader;
|
|
}
|