372 lines
14 KiB
C++
372 lines
14 KiB
C++
#include "DocumentWizard.h"
|
||
|
||
#include <QVBoxLayout>
|
||
#include <QHBoxLayout>
|
||
#include <QLabel>
|
||
#include <QListWidget>
|
||
#include <QPushButton>
|
||
#include <QLineEdit>
|
||
#include <QComboBox>
|
||
#include <QSpinBox>
|
||
#include <QStackedWidget>
|
||
#include <QGroupBox>
|
||
#include <QFileDialog>
|
||
#include <QMessageBox>
|
||
#include <QFrame>
|
||
#include <QFont>
|
||
#include <QScrollArea>
|
||
|
||
// ============================================================
|
||
// Парсинг шаблонных строк
|
||
//
|
||
// Формат строки:
|
||
// heading1|Введите название раздела|Вариант А;Вариант Б;Вариант В
|
||
// text|Выберите описание|Короткое;Подробное
|
||
// table|Заполните таблицу характеристик|
|
||
// image|Вставьте изображение схемы|
|
||
//
|
||
// Если choices пуст — пользователь вводит текст вручную.
|
||
// ============================================================
|
||
|
||
DocumentWizard::DocumentWizard(const QStringList& templateLines, QWidget* parent)
|
||
: QDialog(parent)
|
||
{
|
||
setWindowTitle("Мастер создания документа");
|
||
setMinimumSize(640, 520);
|
||
setupUi();
|
||
parseTemplates(templateLines);
|
||
|
||
if (!m_steps.isEmpty())
|
||
showStep(0);
|
||
}
|
||
|
||
void DocumentWizard::parseTemplates(const QStringList& lines) {
|
||
m_steps.clear();
|
||
|
||
static const QMap<QString, DocElementType> typeMap = {
|
||
{"heading1", DocElementType::Heading1},
|
||
{"heading2", DocElementType::Heading2},
|
||
{"heading3", DocElementType::Heading3},
|
||
{"text", DocElementType::NormalText},
|
||
{"table", DocElementType::Table},
|
||
{"image", DocElementType::Image},
|
||
};
|
||
|
||
for (const QString& rawLine : lines) {
|
||
QString line = rawLine.trimmed();
|
||
if (line.isEmpty() || line.startsWith('#'))
|
||
continue;
|
||
|
||
QStringList parts = line.split('|');
|
||
if (parts.size() < 2)
|
||
continue;
|
||
|
||
WizardStep step;
|
||
step.elementType = typeMap.value(parts[0].trimmed().toLower(),
|
||
DocElementType::NormalText);
|
||
step.prompt = parts[1].trimmed();
|
||
|
||
if (parts.size() >= 3 && !parts[2].trimmed().isEmpty()) {
|
||
for (const QString& ch : parts[2].split(';'))
|
||
step.choices << ch.trimmed();
|
||
}
|
||
|
||
m_steps.append(step);
|
||
}
|
||
}
|
||
|
||
// ============================================================
|
||
// UI
|
||
// ============================================================
|
||
|
||
void DocumentWizard::setupUi() {
|
||
auto* mainLayout = new QVBoxLayout(this);
|
||
mainLayout->setSpacing(12);
|
||
mainLayout->setContentsMargins(16, 16, 16, 16);
|
||
|
||
// --- Заголовок шага ---
|
||
m_stepLabel = new QLabel(this);
|
||
QFont stepFont = m_stepLabel->font();
|
||
stepFont.setBold(true);
|
||
stepFont.setPointSize(11);
|
||
m_stepLabel->setFont(stepFont);
|
||
m_stepLabel->setStyleSheet("color: #2c5f8a;");
|
||
mainLayout->addWidget(m_stepLabel);
|
||
|
||
// --- Подсказка ---
|
||
m_promptLabel = new QLabel(this);
|
||
m_promptLabel->setWordWrap(true);
|
||
m_promptLabel->setStyleSheet("background:#f0f4f8; padding:8px; border-radius:4px;");
|
||
mainLayout->addWidget(m_promptLabel);
|
||
|
||
// --- Стек страниц контента ---
|
||
m_contentStack = new QStackedWidget(this);
|
||
mainLayout->addWidget(m_contentStack, 1);
|
||
|
||
// -- Страница 0: список вариантов + свободный ввод --
|
||
auto* choicePage = new QWidget;
|
||
auto* choiceLayout = new QVBoxLayout(choicePage);
|
||
choiceLayout->setContentsMargins(0,0,0,0);
|
||
|
||
m_choiceList = new QListWidget;
|
||
m_choiceList->setAlternatingRowColors(true);
|
||
choiceLayout->addWidget(new QLabel("Выберите вариант:"));
|
||
choiceLayout->addWidget(m_choiceList);
|
||
choiceLayout->addWidget(new QLabel("Или введите свой текст:"));
|
||
m_freeInput = new QLineEdit;
|
||
m_freeInput->setPlaceholderText("Введите текст вручную…");
|
||
choiceLayout->addWidget(m_freeInput);
|
||
m_contentStack->addWidget(choicePage); // index 0
|
||
|
||
// -- Страница 1: таблица --
|
||
auto* tablePage = new QWidget;
|
||
auto* tableLayout = new QVBoxLayout(tablePage);
|
||
tableLayout->setContentsMargins(0,0,0,0);
|
||
|
||
tableLayout->addWidget(new QLabel("Заголовки столбцов (через ';'):"));
|
||
m_tableHeadersEdit = new QLineEdit;
|
||
m_tableHeadersEdit->setPlaceholderText("Параметр;Значение;Описание");
|
||
tableLayout->addWidget(m_tableHeadersEdit);
|
||
|
||
tableLayout->addWidget(new QLabel("Строки (добавьте каждую строку; значения через ';'):"));
|
||
m_tableRowsList = new QListWidget;
|
||
tableLayout->addWidget(m_tableRowsList, 1);
|
||
|
||
auto* addRowLayout = new QHBoxLayout;
|
||
m_tableRowInput = new QLineEdit;
|
||
m_tableRowInput->setPlaceholderText("Значение1;Значение2;Значение3");
|
||
m_addRowBtn = new QPushButton("+ Добавить строку");
|
||
m_removeRowBtn = new QPushButton("✕ Удалить");
|
||
addRowLayout->addWidget(m_tableRowInput, 1);
|
||
addRowLayout->addWidget(m_addRowBtn);
|
||
addRowLayout->addWidget(m_removeRowBtn);
|
||
tableLayout->addLayout(addRowLayout);
|
||
m_contentStack->addWidget(tablePage); // index 1
|
||
|
||
// -- Страница 2: изображение --
|
||
auto* imgPage = new QWidget;
|
||
auto* imgLayout = new QVBoxLayout(imgPage);
|
||
imgLayout->setContentsMargins(0,0,0,0);
|
||
|
||
imgLayout->addWidget(new QLabel("Путь к изображению:"));
|
||
auto* imgRowLayout = new QHBoxLayout;
|
||
m_imagePathEdit = new QLineEdit;
|
||
m_imagePathEdit->setPlaceholderText("/путь/к/файлу.png");
|
||
m_browseBtn = new QPushButton("Обзор…");
|
||
imgRowLayout->addWidget(m_imagePathEdit, 1);
|
||
imgRowLayout->addWidget(m_browseBtn);
|
||
imgLayout->addLayout(imgRowLayout);
|
||
|
||
auto* dimLayout = new QHBoxLayout;
|
||
dimLayout->addWidget(new QLabel("Ширина (см):"));
|
||
m_imgWidthCm = new QSpinBox; m_imgWidthCm->setRange(1, 30); m_imgWidthCm->setValue(10);
|
||
dimLayout->addWidget(m_imgWidthCm);
|
||
dimLayout->addWidget(new QLabel("Высота (см):"));
|
||
m_imgHeightCm = new QSpinBox; m_imgHeightCm->setRange(1, 30); m_imgHeightCm->setValue(7);
|
||
dimLayout->addWidget(m_imgHeightCm);
|
||
dimLayout->addStretch();
|
||
imgLayout->addLayout(dimLayout);
|
||
imgLayout->addStretch();
|
||
m_contentStack->addWidget(imgPage); // index 2
|
||
|
||
// --- Прогресс ---
|
||
m_progressLabel = new QLabel(this);
|
||
m_progressLabel->setAlignment(Qt::AlignRight);
|
||
m_progressLabel->setStyleSheet("color: #888;");
|
||
mainLayout->addWidget(m_progressLabel);
|
||
|
||
// --- Кнопки навигации ---
|
||
auto* btnLayout = new QHBoxLayout;
|
||
m_backBtn = new QPushButton("◀ Назад");
|
||
m_nextBtn = new QPushButton("Далее ▶");
|
||
m_finishBtn = new QPushButton("💾 Сохранить документ");
|
||
m_finishBtn->setStyleSheet("background:#2c5f8a; color:white; font-weight:bold; padding:6px 18px;");
|
||
m_finishBtn->setVisible(false);
|
||
|
||
btnLayout->addWidget(m_backBtn);
|
||
btnLayout->addStretch();
|
||
btnLayout->addWidget(m_nextBtn);
|
||
btnLayout->addWidget(m_finishBtn);
|
||
mainLayout->addLayout(btnLayout);
|
||
|
||
// --- Соединения ---
|
||
connect(m_choiceList, &QListWidget::currentRowChanged,
|
||
this, &DocumentWizard::onChoiceSelected);
|
||
connect(m_addRowBtn, &QPushButton::clicked, this, &DocumentWizard::onAddTableRow);
|
||
connect(m_removeRowBtn, &QPushButton::clicked, this, &DocumentWizard::onRemoveTableRow);
|
||
connect(m_browseBtn, &QPushButton::clicked, this, &DocumentWizard::onBrowseImage);
|
||
connect(m_backBtn, &QPushButton::clicked, this, &DocumentWizard::onBackClicked);
|
||
connect(m_nextBtn, &QPushButton::clicked, this, &DocumentWizard::onNextClicked);
|
||
connect(m_finishBtn, &QPushButton::clicked, this, &DocumentWizard::onFinishClicked);
|
||
}
|
||
|
||
// ============================================================
|
||
// Отображение шага
|
||
// ============================================================
|
||
|
||
void DocumentWizard::showStep(int index) {
|
||
if (index < 0 || index >= m_steps.size()) return;
|
||
m_currentStep = index;
|
||
|
||
const WizardStep& step = m_steps[index];
|
||
|
||
m_stepLabel->setText(QString("Шаг %1 из %2").arg(index + 1).arg(m_steps.size()));
|
||
m_promptLabel->setText(step.prompt);
|
||
m_progressLabel->setText(QString("%1 / %2 шагов выполнено").arg(index).arg(m_steps.size()));
|
||
|
||
m_backBtn->setEnabled(index > 0);
|
||
bool isLast = (index == m_steps.size() - 1);
|
||
m_nextBtn->setVisible(!isLast);
|
||
m_finishBtn->setVisible(isLast);
|
||
|
||
// Настраиваем страницу в зависимости от типа
|
||
if (step.elementType == DocElementType::Table) {
|
||
m_contentStack->setCurrentIndex(1);
|
||
m_tableRowsList->clear();
|
||
m_tableHeadersEdit->clear();
|
||
m_tableRowInput->clear();
|
||
} else if (step.elementType == DocElementType::Image) {
|
||
m_contentStack->setCurrentIndex(2);
|
||
m_imagePathEdit->clear();
|
||
} else {
|
||
m_contentStack->setCurrentIndex(0);
|
||
m_choiceList->clear();
|
||
m_freeInput->clear();
|
||
for (const QString& ch : step.choices)
|
||
m_choiceList->addItem(ch);
|
||
// Если вариантов нет — скрываем список
|
||
m_choiceList->setVisible(!step.choices.isEmpty());
|
||
}
|
||
}
|
||
|
||
// ============================================================
|
||
// Сбор данных текущего шага и добавление в DocxBuilder
|
||
// ============================================================
|
||
|
||
void DocumentWizard::applyCurrentStep() {
|
||
const WizardStep& step = m_steps[m_currentStep];
|
||
|
||
switch (step.elementType) {
|
||
case DocElementType::Heading1:
|
||
case DocElementType::Heading2:
|
||
case DocElementType::Heading3: {
|
||
QString text = m_freeInput->text().trimmed();
|
||
if (text.isEmpty() && m_choiceList->currentItem())
|
||
text = m_choiceList->currentItem()->text();
|
||
if (text.isEmpty()) {
|
||
QMessageBox::warning(this, "Пусто", "Пожалуйста, введите или выберите текст заголовка.");
|
||
return;
|
||
}
|
||
int level = (step.elementType == DocElementType::Heading1) ? 1
|
||
: (step.elementType == DocElementType::Heading2) ? 2 : 3;
|
||
m_builder.addHeading(text, level);
|
||
break;
|
||
}
|
||
case DocElementType::NormalText: {
|
||
QString text = m_freeInput->text().trimmed();
|
||
if (text.isEmpty() && m_choiceList->currentItem())
|
||
text = m_choiceList->currentItem()->text();
|
||
if (text.isEmpty()) {
|
||
QMessageBox::warning(this, "Пусто", "Пожалуйста, введите или выберите текст.");
|
||
return;
|
||
}
|
||
DocStyle style;
|
||
style.font = "Times New Roman";
|
||
style.fontSize = 24; // 12pt
|
||
m_builder.addParagraph(text, style);
|
||
break;
|
||
}
|
||
case DocElementType::Table: {
|
||
TableData td;
|
||
QString hdr = m_tableHeadersEdit->text().trimmed();
|
||
if (!hdr.isEmpty())
|
||
td.headers = hdr.split(';');
|
||
for (int i = 0; i < m_tableRowsList->count(); ++i)
|
||
td.rows.append(m_tableRowsList->item(i)->text().split(';'));
|
||
if (td.headers.isEmpty() && td.rows.isEmpty()) {
|
||
QMessageBox::warning(this, "Таблица пуста", "Добавьте хотя бы одну строку в таблицу.");
|
||
return;
|
||
}
|
||
m_builder.addTable(td);
|
||
break;
|
||
}
|
||
case DocElementType::Image: {
|
||
QString path = m_imagePathEdit->text().trimmed();
|
||
if (path.isEmpty()) {
|
||
QMessageBox::warning(this, "Нет файла", "Выберите изображение.");
|
||
return;
|
||
}
|
||
// Конвертируем сантиметры в EMU (1 см = 360000 EMU)
|
||
int wEmu = m_imgWidthCm->value() * 360000;
|
||
int hEmu = m_imgHeightCm->value() * 360000;
|
||
m_builder.addImage(path, wEmu, hEmu);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
// ============================================================
|
||
// Слоты навигации
|
||
// ============================================================
|
||
|
||
void DocumentWizard::onNextClicked() {
|
||
applyCurrentStep();
|
||
if (m_currentStep < m_steps.size() - 1)
|
||
showStep(m_currentStep + 1);
|
||
}
|
||
|
||
void DocumentWizard::onBackClicked() {
|
||
if (m_currentStep > 0)
|
||
showStep(m_currentStep - 1);
|
||
}
|
||
|
||
void DocumentWizard::onFinishClicked() {
|
||
applyCurrentStep();
|
||
|
||
QString path = QFileDialog::getSaveFileName(
|
||
this, "Сохранить документ", "document.docx",
|
||
"Word Documents (*.docx)");
|
||
|
||
if (path.isEmpty()) return;
|
||
|
||
if (m_builder.save(path)) {
|
||
m_outputPath = path;
|
||
QMessageBox::information(this, "Готово",
|
||
QString("Документ успешно сохранён:\n%1").arg(path));
|
||
accept();
|
||
} else {
|
||
QMessageBox::critical(this, "Ошибка",
|
||
"Не удалось сохранить документ. Проверьте путь и права доступа.");
|
||
}
|
||
}
|
||
|
||
// ============================================================
|
||
// Слоты работы с таблицей и изображением
|
||
// ============================================================
|
||
|
||
void DocumentWizard::onChoiceSelected(int row) {
|
||
if (row >= 0 && row < m_choiceList->count())
|
||
m_freeInput->setText(m_choiceList->item(row)->text());
|
||
}
|
||
|
||
void DocumentWizard::onAddTableRow() {
|
||
QString rowText = m_tableRowInput->text().trimmed();
|
||
if (!rowText.isEmpty()) {
|
||
m_tableRowsList->addItem(rowText);
|
||
m_tableRowInput->clear();
|
||
}
|
||
}
|
||
|
||
void DocumentWizard::onRemoveTableRow() {
|
||
auto* item = m_tableRowsList->currentItem();
|
||
if (item) delete item;
|
||
}
|
||
|
||
void DocumentWizard::onBrowseImage() {
|
||
QString path = QFileDialog::getOpenFileName(
|
||
this, "Выбрать изображение", QString(),
|
||
"Изображения (*.png *.jpg *.jpeg *.bmp)");
|
||
if (!path.isEmpty())
|
||
m_imagePathEdit->setText(path);
|
||
}
|