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.

This commit is contained in:
2025-12-30 11:53:08 +03:00
parent 80e407e6be
commit 84b7eceb9f
5 changed files with 552 additions and 24 deletions
+412
View File
@@ -23,6 +23,9 @@ Q_DECLARE_METATYPE(VedomostCellData)
#include <QMenu>
#include <QList>
#include <algorithm>
#include <QApplication>
#include <QClipboard>
#include <QMimeData>
// 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<int> 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<PerechenProcessor*>(m_processor);
SpecificationPCBProcessor *specPCBProcessor = qobject_cast<SpecificationPCBProcessor*>(m_processor);
SpecificationProcessor *specProcessor = qobject_cast<SpecificationProcessor*>(m_processor);
VedomostProcessor *vedomostProcessor = qobject_cast<VedomostProcessor*>(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<int> 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<PerechenProcessor*>(m_processor);
SpecificationPCBProcessor *specPCBProcessor = qobject_cast<SpecificationPCBProcessor*>(m_processor);
SpecificationProcessor *specProcessor = qobject_cast<SpecificationProcessor*>(m_processor);
VedomostProcessor *vedomostProcessor = qobject_cast<VedomostProcessor*>(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();
}
+18
View File
@@ -9,6 +9,8 @@
#include <QHBoxLayout>
#include <QMenu>
#include <QAction>
#include <QClipboard>
#include <QMimeData>
#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;