#include "DocumentWizard.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // ============================================================ // Парсинг шаблонных строк // // Формат строки: // 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 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); }