From 84b7eceb9f8657da3408ea0d672bc98430de5c24 Mon Sep 17 00:00:00 2001 From: grigo Date: Tue, 30 Dec 2025 11:53:08 +0300 Subject: [PATCH] Enhance document editing capabilities by adding context menu options for copying, cutting, and pasting rows and cells in DocumentEditWidget. Refactor related methods for improved clipboard handling and data management. Introduce new helper functions for better code organization. --- app_icon.rc | 2 + controller/specificationpcbcontroller.cpp | 143 ++++++-- controller/specificationpcbcontroller.h | 1 + view/documentprocessor.cpp | 412 ++++++++++++++++++++++ view/documentprocessor.h | 18 + 5 files changed, 552 insertions(+), 24 deletions(-) diff --git a/app_icon.rc b/app_icon.rc index c133b1c..ce41c89 100644 --- a/app_icon.rc +++ b/app_icon.rc @@ -11,3 +11,5 @@ IDI_ICON1 ICON DISCARDABLE "assets/icon2.ico" + + diff --git a/controller/specificationpcbcontroller.cpp b/controller/specificationpcbcontroller.cpp index 9421fcb..cc5880b 100644 --- a/controller/specificationpcbcontroller.cpp +++ b/controller/specificationpcbcontroller.cpp @@ -297,20 +297,6 @@ void SpecificationPCBController::generateTableFromComponents() QList componentsInGroup = it.value(); if (componentsInGroup.size() > 1) { - // Если компонентов больше одного, добавляем заголовок типа - SpecificationPCBRowData headerRow; - headerRow.isHeader = true; - headerRow.format = SpecificationPCBCellData("", 0, 1, true, false, false, 100, true); - headerRow.zone = SpecificationPCBCellData("", 0, 1, true, false, false, 100, true); - headerRow.position = SpecificationPCBCellData("", 0, 1, true, false, false, 100, true); - headerRow.designation = SpecificationPCBCellData("", 0, 1, true, false, false, 100, true); - // Заголовок с componentType не должен подчеркиваться - headerRow.name = SpecificationPCBCellData(getComponentType(componentsInGroup.first()), 0, 1, true, false, false, 100, false); - headerRow.quantity = SpecificationPCBCellData("", 0, 1, true, false, false, 100, true); - headerRow.note = SpecificationPCBCellData("", 0, 1, true, false, false, 100, true); - m_tableModel->addEmptyRow(); - m_tableModel->addRow(headerRow); - m_tableModel->addEmptyRow(); // Сначала группируем компоненты по наименованию QList>> groupedByName; for (ComponentModel *component : componentsInGroup) { @@ -450,11 +436,51 @@ void SpecificationPCBController::generateTableFromComponents() } } - // Разбиваем длинные группы десигнаторов (11 символов на строку) - QStringList designatorsSplit = splitDesignatorGroups(designatorGroupsForThisName, 11); + // Объединяем все группы дезигнаторов в одну строку через запятые + QString allDesignators = designatorGroupsForThisName.join(", "); - // Определяем максимальное количество строк для названия и десигнаторов - int maxRows = qMax(designationParts.size(), designatorsSplit.size()); + // Разбиваем длинные группы десигнаторов используя логику комплексных строк (11 символов на строку) + QList designatorsSplitList = splitComplexStringByMiddle(allDesignators, 11); + QStringList designatorsSplit; + for (const QString &str : designatorsSplitList) { + designatorsSplit.append(str); + } + + // Получаем тип компонента: если количество > 1, используем множественное число, иначе единственное + QString componentType; + if (totalQuantity > 1) { + componentType = getComponentType(firstComponent); + } else { + componentType = getComponentTypeSingular(firstComponent); + } + + // Формируем список строк названия с учетом типа компонента + QList nameParts; + if (!componentType.isEmpty() && !designationParts.isEmpty()) { + // Проверяем, помещается ли тип компонента + первая часть названия + QString firstPartWithType = componentType + " " + designationParts[0]; + if (firstPartWithType.length() <= 34) { + // Помещается - добавляем тип в первую строку + nameParts.append(firstPartWithType); + // Добавляем остальные части + for (int i = 1; i < designationParts.size(); ++i) { + nameParts.append(designationParts[i]); + } + } else { + // Не помещается - выносим тип в отдельную строку + nameParts.append(componentType); + // Добавляем все части названия + for (const QString &part : designationParts) { + nameParts.append(part); + } + } + } else { + // Типа нет или нет частей названия - используем как есть + nameParts = designationParts; + } + + // Определяем максимальное количество строк для названия и дезигнаторов + int maxRows = qMax(nameParts.size(), designatorsSplit.size()); for (int rowIndex = 0; rowIndex < maxRows; ++rowIndex) { SpecificationPCBRowData row; @@ -469,9 +495,9 @@ void SpecificationPCBController::generateTableFromComponents() row.quantity = SpecificationPCBCellData("", 0, 1, false, false, false, 100, false); } - // Заполняем название (если есть) - if (rowIndex < designationParts.size()) { - row.name = SpecificationPCBCellData(designationParts[rowIndex], 0, 1, false, false, false, 100, false); + // Заполняем название + if (rowIndex < nameParts.size()) { + row.name = SpecificationPCBCellData(nameParts[rowIndex], 0, 1, false, false, false, 100, false); } else { row.name = SpecificationPCBCellData("", 0, 1, false, false, false, 100, false); } @@ -496,23 +522,63 @@ void SpecificationPCBController::generateTableFromComponents() ComponentModel *component = componentsInGroup.first(); QString currentDesignation = getComponentValue(component, "ManufacturerPartNumber"); + // Получаем тип компонента в единственном числе (количество = 1) + QString componentType = getComponentTypeSingular(component); + // Разделяем длинное наименование на несколько строк QMap props = component->properties(); QString originalString = props.value("ManufacturerPartNumber", ""); QList designationParts = splitLongDesignation(currentDesignation, originalString, component); - for (int partIndex = 0; partIndex < designationParts.size(); ++partIndex) { + // Формируем список строк названия с учетом типа компонента + QList nameParts; + if (!componentType.isEmpty() && !designationParts.isEmpty()) { + // Проверяем, помещается ли тип компонента + первая часть названия + QString firstPartWithType = componentType + " " + designationParts[0]; + if (firstPartWithType.length() <= 34) { + // Помещается - добавляем тип в первую строку + nameParts.append(firstPartWithType); + // Добавляем остальные части + for (int i = 1; i < designationParts.size(); ++i) { + nameParts.append(designationParts[i]); + } + } else { + // Не помещается - выносим тип в отдельную строку + nameParts.append(componentType); + // Добавляем все части названия + for (const QString &part : designationParts) { + nameParts.append(part); + } + } + } else { + // Типа нет или нет частей названия - используем как есть + nameParts = designationParts; + } + + // Обрабатываем дезигнатор: если он длинный, разбиваем используя логику комплексных строк + QString designatorStr = component->designator(); + QList designatorParts = splitComplexStringByMiddle(designatorStr, 11); + + // Определяем максимальное количество строк для названия и дезигнатора + int maxParts = qMax(nameParts.size(), designatorParts.size()); + + for (int partIndex = 0; partIndex < maxParts; ++partIndex) { SpecificationPCBRowData row; // Только в первой строке показываем позицию и количество if (partIndex == 0) { row.position = SpecificationPCBCellData(QString::number(pos1++), 0, 1, false, false, false, 100, false); row.quantity = SpecificationPCBCellData("1", 0, 1, false, false, false, 100, false); - row.note = SpecificationPCBCellData(component->designator(), 0, 1, false, false, false, 100, false); } else { // В последующих строках оставляем пустыми позицию и количество row.position = SpecificationPCBCellData("", 0, 1, false, false, false, 100, false); row.quantity = SpecificationPCBCellData("", 0, 1, false, false, false, 100, false); + } + + // Заполняем дезигнатор (если есть) + if (partIndex < designatorParts.size()) { + row.note = SpecificationPCBCellData(designatorParts[partIndex], 0, 1, false, false, false, 100, false); + } else { row.note = SpecificationPCBCellData("", 0, 1, false, false, false, 100, false); } @@ -520,7 +586,13 @@ void SpecificationPCBController::generateTableFromComponents() row.format = SpecificationPCBCellData("", 0, 1, false, false, false, 100, false); row.zone = SpecificationPCBCellData("", 0, 1, false, false, false, 100, false); row.designation = SpecificationPCBCellData("", 0, 1, false, false, false, 100, false); - row.name = SpecificationPCBCellData(designationParts[partIndex], 0, 1, false, false, false, 100, false); + + // Заполняем название + if (partIndex < nameParts.size()) { + row.name = SpecificationPCBCellData(nameParts[partIndex], 0, 1, false, false, false, 100, false); + } else { + row.name = SpecificationPCBCellData("", 0, 1, false, false, false, 100, false); + } m_tableModel->addRow(row); } @@ -973,6 +1045,29 @@ QString SpecificationPCBController::getComponentType(ComponentModel *component) return component->designator(); } +QString SpecificationPCBController::getComponentTypeSingular(ComponentModel *component) +{ + if (!component) return ""; + + // Получаем тип компонента из маппинга дезигнаторов в единственном числе + if (m_designatorMappingModel) { + QString componentType = m_designatorMappingModel->getComponentType(component->designator(), false); // false для единственного числа + if (!componentType.isEmpty()) { + return componentType; + } + } + + // Если маппинг не найден, используем тип из свойств компонента + QMap props = component->properties(); + QString type = props.value("Type", ""); + if (!type.isEmpty()) { + return type; + } + + // Если и тип не найден, возвращаем пустую строку + return QString(); +} + QString SpecificationPCBController::getComponentFormat(ComponentModel *component) { if (!component) return ""; diff --git a/controller/specificationpcbcontroller.h b/controller/specificationpcbcontroller.h index cea27ca..dac2c40 100644 --- a/controller/specificationpcbcontroller.h +++ b/controller/specificationpcbcontroller.h @@ -83,6 +83,7 @@ private: // Методы для группировки QString getComponentType(ComponentModel *component); + QString getComponentTypeSingular(ComponentModel *component); // Метод для разделения десигнатора на буквенную и цифровую части QPair splitDesignator(const QString &designator); diff --git a/view/documentprocessor.cpp b/view/documentprocessor.cpp index d162d11..9190b2e 100644 --- a/view/documentprocessor.cpp +++ b/view/documentprocessor.cpp @@ -23,6 +23,9 @@ Q_DECLARE_METATYPE(VedomostCellData) #include #include #include +#include +#include +#include // DocumentProcessor DocumentProcessor::DocumentProcessor(QObject *parent) @@ -1617,6 +1620,41 @@ void DocumentEditWidget::onContextMenuRequested(const QPoint &pos) contextMenu.addSeparator(); + // Меню для работы со строками + QMenu *rowsMenu = contextMenu.addMenu("Строки"); + QAction *copyRowsAction = rowsMenu->addAction("Копировать строки"); + copyRowsAction->setEnabled(selectedCount > 0); + connect(copyRowsAction, &QAction::triggered, this, &DocumentEditWidget::onContextMenuCopyRows); + + QAction *cutRowsAction = rowsMenu->addAction("Вырезать строки"); + cutRowsAction->setEnabled(selectedCount > 0); + connect(cutRowsAction, &QAction::triggered, this, &DocumentEditWidget::onContextMenuCutRows); + + QAction *pasteRowsAction = rowsMenu->addAction("Вставить строки"); + QClipboard *clipboard = QApplication::clipboard(); + const QMimeData *mimeData = clipboard->mimeData(); + pasteRowsAction->setEnabled(mimeData && mimeData->hasText()); + connect(pasteRowsAction, &QAction::triggered, this, &DocumentEditWidget::onContextMenuPasteRows); + + // Меню для работы с ячейками + QMenu *cellsMenu = contextMenu.addMenu("Ячейки"); + QItemSelection cellSelection = selectionModel->selection(); + bool hasCellSelection = !cellSelection.isEmpty(); + + QAction *copyCellsAction = cellsMenu->addAction("Копировать ячейки"); + copyCellsAction->setEnabled(hasCellSelection); + connect(copyCellsAction, &QAction::triggered, this, &DocumentEditWidget::onContextMenuCopyCells); + + QAction *cutCellsAction = cellsMenu->addAction("Вырезать ячейки"); + cutCellsAction->setEnabled(hasCellSelection); + connect(cutCellsAction, &QAction::triggered, this, &DocumentEditWidget::onContextMenuCutCells); + + QAction *pasteCellsAction = cellsMenu->addAction("Вставить ячейки"); + pasteCellsAction->setEnabled(mimeData && mimeData->hasText()); + connect(pasteCellsAction, &QAction::triggered, this, &DocumentEditWidget::onContextMenuPasteCells); + + contextMenu.addSeparator(); + QAction *cellSettingsAction = contextMenu.addAction("Настройка ячейки..."); connect(cellSettingsAction, &QAction::triggered, this, &DocumentEditWidget::onContextMenuCellSettings); @@ -2103,4 +2141,378 @@ void DocumentEditWidget::onContextMenuCellSettings() qDebug() << "DocumentEditWidget::onContextMenuCellSettings: Ошибка при сохранении настроек в модель - модель не найдена"; } } +} + +// Методы для копирования/вставки/вырезания строк +void DocumentEditWidget::onContextMenuCopyRows() +{ + QItemSelectionModel *selectionModel = m_tableView->selectionModel(); + QModelIndexList selectedRows = selectionModel->selectedRows(); + + if (selectedRows.isEmpty()) { + return; + } + + copyRowsToClipboard(selectedRows); +} + +void DocumentEditWidget::onContextMenuCutRows() +{ + QItemSelectionModel *selectionModel = m_tableView->selectionModel(); + QModelIndexList selectedRows = selectionModel->selectedRows(); + + if (selectedRows.isEmpty()) { + return; + } + + QModelIndex currentIndex = m_tableView->currentIndex(); + int insertPosition = currentIndex.isValid() ? currentIndex.row() : -1; + + cutRows(insertPosition); +} + +void DocumentEditWidget::onContextMenuPasteRows() +{ + QModelIndex currentIndex = m_tableView->currentIndex(); + int insertPosition = currentIndex.isValid() ? currentIndex.row() + 1 : -1; + + if (insertPosition < 0) { + // Если нет текущей позиции, вставляем в конец + QAbstractItemModel *model = m_tableView->model(); + if (model) { + insertPosition = model->rowCount(); + } else { + return; + } + } + + pasteRowsFromClipboard(insertPosition); +} + +// Методы для копирования/вставки/вырезания ячеек +void DocumentEditWidget::onContextMenuCopyCells() +{ + QItemSelectionModel *selectionModel = m_tableView->selectionModel(); + QItemSelection selection = selectionModel->selection(); + + if (selection.isEmpty()) { + return; + } + + copyCellsToClipboard(selection); +} + +void DocumentEditWidget::onContextMenuCutCells() +{ + QModelIndex currentIndex = m_tableView->currentIndex(); + + if (!currentIndex.isValid()) { + return; + } + + cutCells(currentIndex); +} + +void DocumentEditWidget::onContextMenuPasteCells() +{ + QModelIndex currentIndex = m_tableView->currentIndex(); + + if (!currentIndex.isValid()) { + return; + } + + pasteCellsFromClipboard(currentIndex); +} + +// Вспомогательные методы для копирования/вставки строк +void DocumentEditWidget::copyRowsToClipboard(const QModelIndexList &rows) +{ + if (rows.isEmpty() || !m_tableView->model()) { + return; + } + + QAbstractItemModel *model = m_tableView->model(); + int columnCount = model->columnCount(); + + // Собираем номера строк и сортируем их + QList rowNumbers; + for (const QModelIndex &index : rows) { + if (index.isValid()) { + rowNumbers.append(index.row()); + } + } + std::sort(rowNumbers.begin(), rowNumbers.end()); + rowNumbers.erase(std::unique(rowNumbers.begin(), rowNumbers.end()), rowNumbers.end()); + + // Формируем текст в формате CSV (табуляция для разделения столбцов, перенос строки для строк) + QStringList rowTexts; + for (int row : rowNumbers) { + QStringList cellTexts; + for (int col = 0; col < columnCount; ++col) { + QModelIndex cellIndex = model->index(row, col); + QString cellText = model->data(cellIndex, Qt::DisplayRole).toString(); + // Заменяем переносы строк и табуляции на пробелы для совместимости + cellText.replace('\n', ' ').replace('\r', ' ').replace('\t', ' '); + cellTexts.append(cellText); + } + rowTexts.append(cellTexts.join("\t")); + } + + QString clipboardText = rowTexts.join("\n"); + + // Копируем в буфер обмена + QClipboard *clipboard = QApplication::clipboard(); + clipboard->setText(clipboardText); + + qDebug() << "DocumentEditWidget::copyRowsToClipboard: Скопировано строк:" << rowNumbers.size(); +} + +void DocumentEditWidget::pasteRowsFromClipboard(int insertPosition) +{ + QClipboard *clipboard = QApplication::clipboard(); + QString clipboardText = clipboard->text(); + + if (clipboardText.isEmpty()) { + return; + } + + QAbstractItemModel *model = m_tableView->model(); + if (!model) { + return; + } + + // Парсим текст из буфера обмена + QStringList rows = clipboardText.split('\n', Qt::SkipEmptyParts); + if (rows.isEmpty()) { + return; + } + + int columnCount = model->columnCount(); + + // Определяем, какой контроллер использовать для добавления строк + PerechenProcessor *perechenProcessor = qobject_cast(m_processor); + SpecificationPCBProcessor *specPCBProcessor = qobject_cast(m_processor); + SpecificationProcessor *specProcessor = qobject_cast(m_processor); + VedomostProcessor *vedomostProcessor = qobject_cast(m_processor); + + // Вставляем строки в обратном порядке, чтобы позиция вставки не смещалась + // и порядок строк сохранялся правильно + for (int i = rows.size() - 1; i >= 0; --i) { + QStringList cells = rows[i].split('\t'); + + // Добавляем пустую строку через соответствующий контроллер + if (perechenProcessor && m_controller && m_controller->perechenTableController()) { + m_controller->perechenTableController()->addEmptyRowAt(insertPosition); + // Заполняем ячейки вставленной строки + for (int col = 0; col < qMin(columnCount, cells.size()); ++col) { + QModelIndex cellIndex = model->index(insertPosition, col); + if (cellIndex.isValid()) { + model->setData(cellIndex, cells[col].trimmed(), Qt::EditRole); + } + } + } else if (specPCBProcessor && m_controller && m_controller->specificationPCBController()) { + m_controller->specificationPCBController()->addEmptyRowAt(insertPosition); + for (int col = 0; col < qMin(columnCount, cells.size()); ++col) { + QModelIndex cellIndex = model->index(insertPosition, col); + if (cellIndex.isValid()) { + model->setData(cellIndex, cells[col].trimmed(), Qt::EditRole); + } + } + } else if (specProcessor && m_controller && m_controller->specificationController()) { + m_controller->specificationController()->addEmptyRowAt(insertPosition); + for (int col = 0; col < qMin(columnCount, cells.size()); ++col) { + QModelIndex cellIndex = model->index(insertPosition, col); + if (cellIndex.isValid()) { + model->setData(cellIndex, cells[col].trimmed(), Qt::EditRole); + } + } + } else if (vedomostProcessor && m_controller && m_controller->vedomostController()) { + m_controller->vedomostController()->addEmptyRowAt(insertPosition); + for (int col = 0; col < qMin(columnCount, cells.size()); ++col) { + QModelIndex cellIndex = model->index(insertPosition, col); + if (cellIndex.isValid()) { + model->setData(cellIndex, cells[col].trimmed(), Qt::EditRole); + } + } + } + } + + qDebug() << "DocumentEditWidget::pasteRowsFromClipboard: Вставлено строк:" << rows.size() << "в позицию:" << insertPosition; +} + +void DocumentEditWidget::cutRows(int insertPosition) +{ + QItemSelectionModel *selectionModel = m_tableView->selectionModel(); + QModelIndexList selectedRows = selectionModel->selectedRows(); + + if (selectedRows.isEmpty()) { + return; + } + + // Копируем строки + copyRowsToClipboard(selectedRows); + + // Удаляем строки (в обратном порядке) + QList rowNumbers; + for (const QModelIndex &index : selectedRows) { + if (index.isValid()) { + rowNumbers.append(index.row()); + } + } + std::sort(rowNumbers.begin(), rowNumbers.end()); + rowNumbers.erase(std::unique(rowNumbers.begin(), rowNumbers.end()), rowNumbers.end()); + std::reverse(rowNumbers.begin(), rowNumbers.end()); + + // Удаляем строки через соответствующий контроллер + PerechenProcessor *perechenProcessor = qobject_cast(m_processor); + SpecificationPCBProcessor *specPCBProcessor = qobject_cast(m_processor); + SpecificationProcessor *specProcessor = qobject_cast(m_processor); + VedomostProcessor *vedomostProcessor = qobject_cast(m_processor); + + for (int row : rowNumbers) { + if (perechenProcessor && m_controller && m_controller->perechenTableController()) { + m_controller->perechenTableController()->removeRow(row); + } else if (specPCBProcessor && m_controller && m_controller->specificationPCBController()) { + m_controller->specificationPCBController()->removeRow(row); + } else if (specProcessor && m_controller && m_controller->specificationController()) { + m_controller->specificationController()->removeRow(row); + } else if (vedomostProcessor && m_controller && m_controller->vedomostController()) { + m_controller->vedomostController()->removeRow(row); + } + } + + qDebug() << "DocumentEditWidget::cutRows: Вырезано строк:" << rowNumbers.size(); +} + +// Вспомогательные методы для копирования/вставки ячеек +void DocumentEditWidget::copyCellsToClipboard(const QItemSelection &selection) +{ + if (selection.isEmpty() || !m_tableView->model()) { + return; + } + + QAbstractItemModel *model = m_tableView->model(); + QModelIndexList indexes = selection.indexes(); + + if (indexes.isEmpty()) { + return; + } + + // Определяем границы выбранной области + int minRow = indexes.first().row(); + int maxRow = indexes.first().row(); + int minCol = indexes.first().column(); + int maxCol = indexes.first().column(); + + for (const QModelIndex &index : indexes) { + minRow = qMin(minRow, index.row()); + maxRow = qMax(maxRow, index.row()); + minCol = qMin(minCol, index.column()); + maxCol = qMax(maxCol, index.column()); + } + + // Формируем текст в формате таблицы (табуляция для столбцов, перенос строки для строк) + QStringList rowTexts; + for (int row = minRow; row <= maxRow; ++row) { + QStringList cellTexts; + for (int col = minCol; col <= maxCol; ++col) { + QModelIndex cellIndex = model->index(row, col); + QString cellText; + // Проверяем, есть ли эта ячейка в выделении + if (indexes.contains(cellIndex)) { + cellText = model->data(cellIndex, Qt::DisplayRole).toString(); + } + // Заменяем переносы строк и табуляции на пробелы для совместимости + cellText.replace('\n', ' ').replace('\r', ' ').replace('\t', ' '); + cellTexts.append(cellText); + } + rowTexts.append(cellTexts.join("\t")); + } + + QString clipboardText = rowTexts.join("\n"); + + // Копируем в буфер обмена + QClipboard *clipboard = QApplication::clipboard(); + clipboard->setText(clipboardText); + + qDebug() << "DocumentEditWidget::copyCellsToClipboard: Скопировано ячеек:" << indexes.size(); +} + +void DocumentEditWidget::pasteCellsFromClipboard(const QModelIndex &topLeft) +{ + if (!topLeft.isValid() || !m_tableView->model()) { + return; + } + + QClipboard *clipboard = QApplication::clipboard(); + QString clipboardText = clipboard->text(); + + if (clipboardText.isEmpty()) { + return; + } + + QAbstractItemModel *model = m_tableView->model(); + + // Парсим текст из буфера обмена + QStringList rows = clipboardText.split('\n', Qt::SkipEmptyParts); + if (rows.isEmpty()) { + return; + } + + int startRow = topLeft.row(); + int startCol = topLeft.column(); + int maxRow = model->rowCount() - 1; + int maxCol = model->columnCount() - 1; + + // Вставляем данные, начиная с topLeft + for (int i = 0; i < rows.size(); ++i) { + int currentRow = startRow + i; + if (currentRow > maxRow) { + break; // Достигли конца таблицы + } + + QStringList cells = rows[i].split('\t'); + for (int j = 0; j < cells.size(); ++j) { + int currentCol = startCol + j; + if (currentCol > maxCol) { + break; // Достигли конца строки + } + + QModelIndex cellIndex = model->index(currentRow, currentCol); + if (cellIndex.isValid()) { + model->setData(cellIndex, cells[j].trimmed(), Qt::EditRole); + } + } + } + + qDebug() << "DocumentEditWidget::pasteCellsFromClipboard: Вставлено ячеек из буфера обмена, начиная с row:" << startRow << "col:" << startCol; +} + +void DocumentEditWidget::cutCells(const QModelIndex &topLeft) +{ + if (!topLeft.isValid()) { + return; + } + + QItemSelectionModel *selectionModel = m_tableView->selectionModel(); + QItemSelection selection = selectionModel->selection(); + + if (selection.isEmpty()) { + return; + } + + // Копируем ячейки + copyCellsToClipboard(selection); + + // Очищаем выбранные ячейки + QModelIndexList indexes = selection.indexes(); + QAbstractItemModel *model = m_tableView->model(); + + for (const QModelIndex &index : indexes) { + if (index.isValid()) { + model->setData(index, "", Qt::EditRole); + } + } + + qDebug() << "DocumentEditWidget::cutCells: Вырезано ячеек:" << indexes.size(); } \ No newline at end of file diff --git a/view/documentprocessor.h b/view/documentprocessor.h index a7a79e9..8d09518 100644 --- a/view/documentprocessor.h +++ b/view/documentprocessor.h @@ -9,6 +9,8 @@ #include #include #include +#include +#include #include "../model/componenttablemodel.h" #include "../model/projectparamtablemodel.h" #include "../model/perechentablemodel.h" @@ -217,6 +219,14 @@ private slots: void onContextMenuMergeRows(); // Объединение нескольких выделенных строк (все столбцы) void onContextMenuMergeColumn(); // Объединение только определенного столбца void onContextMenuCellSettings(); + + // Слоты для копирования/вставки/вырезания + void onContextMenuCopyRows(); // Копирование строк + void onContextMenuCutRows(); // Вырезание строк + void onContextMenuPasteRows(); // Вставка строк + void onContextMenuCopyCells(); // Копирование ячеек + void onContextMenuCutCells(); // Вырезание ячеек + void onContextMenuPasteCells(); // Вставка ячеек private: void setUp(); @@ -228,6 +238,14 @@ private: // Вспомогательные методы int getCurrentSelectedRow() const; void applyColumnStretching(); // Метод для применения настроек растягивания столбцов + + // Вспомогательные методы для копирования/вставки + void copyRowsToClipboard(const QModelIndexList &rows); + void pasteRowsFromClipboard(int insertPosition); + void cutRows(int insertPosition); // Вырезает строки (копирует и удаляет) + void copyCellsToClipboard(const QItemSelection &selection); + void pasteCellsFromClipboard(const QModelIndex &topLeft); + void cutCells(const QModelIndex &topLeft); // Вырезает ячейки (копирует и очищает) DocumentProcessor *m_processor; ComponentTableModel *m_componentModel;