#include "panels/DescriptionPanel.hpp" #include #include #include "EmptyAwareComboBox.hpp" #include #include #include #include #include #include #include #include "Theme.hpp" #include "panels/QuillDelta.hpp" namespace geopro::app { namespace { // 字号下拉选项(px)——对照原版 ql-size 的 12~32px。 const int kFontSizesPx[] = {12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32}; // 字体族——对照原版 ql-font:显示名 + Qt family。 struct FontOption { const char* label; const char* family; }; const FontOption kFontFamilies[] = { {"微软雅黑", "Microsoft YaHei"}, {"宋体", "SimSun"}, {"仿宋", "FangSong"}, {"楷体", "KaiTi"}, {"黑体", "SimHei"}, {"仿宋_GB2312", "FangSong"}, }; } // namespace DescriptionPanel::DescriptionPanel(QWidget* parent) : QWidget(parent) { auto* lay = new QVBoxLayout(this); lay->setContentsMargins(geopro::app::space::kMd, geopro::app::space::kMd, geopro::app::space::kMd, geopro::app::space::kMd); lay->setSpacing(geopro::app::space::kSm); auto* tb = new QToolBar(this); buildToolbar(tb); lay->addWidget(tb); edit_ = new QTextEdit(this); edit_->setAcceptRichText(true); edit_->setPlaceholderText(QStringLiteral("暂无描述")); lay->addWidget(edit_, 1); auto* btnLay = new QHBoxLayout(); btnLay->addStretch(); saveBtn_ = new QPushButton(QStringLiteral("保存"), this); saveBtn_->setEnabled(false); // 注入 cmdRepo 后启用 btnLay->addWidget(saveBtn_); lay->addLayout(btnLay); connect(saveBtn_, &QPushButton::clicked, this, [this]() { emit saveRequested(); }); } void DescriptionPanel::buildToolbar(QToolBar* tb) { // 粗体 / 斜体 / 下划线:可勾选按钮,作用于选区当前字符格式。 auto addToggle = [this, tb](const QString& label, auto applier) { auto* btn = new QToolButton(tb); btn->setText(label); btn->setCheckable(true); tb->addWidget(btn); connect(btn, &QToolButton::toggled, this, applier); return btn; }; // 对照原版 Quill 工具栏:粗/斜 + 字色/背景色 + 对齐 + 标题 + 字号 + 字体族。 // (原版无下划线/列表,故此处不设,以贴近原版。) addToggle(QStringLiteral("B"), [this](bool on) { QTextCharFormat f; f.setFontWeight(on ? QFont::Bold : QFont::Normal); edit_->mergeCurrentCharFormat(f); }); addToggle(QStringLiteral("I"), [this](bool on) { QTextCharFormat f; f.setFontItalic(on); edit_->mergeCurrentCharFormat(f); }); // 字色:弹色板,作用于选区前景色。 auto* colorBtn = new QToolButton(tb); colorBtn->setText(QStringLiteral("A")); colorBtn->setToolTip(QStringLiteral("字体颜色")); tb->addWidget(colorBtn); connect(colorBtn, &QToolButton::clicked, this, [this]() { const QColor c = QColorDialog::getColor(Qt::black, this, QStringLiteral("字体颜色")); if (!c.isValid()) return; QTextCharFormat f; f.setForeground(c); edit_->mergeCurrentCharFormat(f); }); // 背景色:弹色板,作用于选区背景色(对照原版 ql-background)。 auto* bgBtn = new QToolButton(tb); bgBtn->setText(QStringLiteral("▢")); bgBtn->setToolTip(QStringLiteral("背景颜色")); tb->addWidget(bgBtn); connect(bgBtn, &QToolButton::clicked, this, [this]() { const QColor c = QColorDialog::getColor(Qt::yellow, this, QStringLiteral("背景颜色")); if (!c.isValid()) return; QTextCharFormat f; f.setBackground(c); edit_->mergeCurrentCharFormat(f); }); // 对齐:左/中/右/两端(对照原版 ql-align)——块级。 auto addAlignBtn = [this, tb](const QString& label, Qt::Alignment align) { auto* btn = new QToolButton(tb); btn->setText(label); tb->addWidget(btn); connect(btn, &QToolButton::clicked, this, [this, align]() { edit_->setAlignment(align); }); }; addAlignBtn(QStringLiteral("⯇"), Qt::AlignLeft); addAlignBtn(QStringLiteral("≡"), Qt::AlignHCenter); addAlignBtn(QStringLiteral("⯈"), Qt::AlignRight); addAlignBtn(QStringLiteral("☰"), Qt::AlignJustify); // 标题下拉(正文 / H1~H4)——块级,作用于当前段。 auto* headerBox = new EmptyAwareComboBox(tb); headerBox->addItem(QStringLiteral("正文"), 0); for (int h = 1; h <= 4; ++h) headerBox->addItem(QStringLiteral("标题%1").arg(h), h); tb->addWidget(headerBox); connect(headerBox, &QComboBox::currentIndexChanged, this, [this, headerBox](int) { const int h = headerBox->currentData().toInt(); QTextCursor cur = edit_->textCursor(); QTextBlockFormat bf = cur.blockFormat(); bf.setHeadingLevel(h); // 0 表示正文。 cur.mergeBlockFormat(bf); edit_->setTextCursor(cur); }); // 字号下拉(px)。 auto* sizeBox = new EmptyAwareComboBox(tb); for (int px : kFontSizesPx) sizeBox->addItem(QStringLiteral("%1px").arg(px), px); sizeBox->setCurrentIndex(2); // 默认 16px(与原版一致)。 tb->addWidget(sizeBox); connect(sizeBox, &QComboBox::currentIndexChanged, this, [this, sizeBox](int) { const int px = sizeBox->currentData().toInt(); QTextCharFormat f; f.setFontPointSize(px * 3.0 / 4.0); // px→pt。 edit_->mergeCurrentCharFormat(f); }); // 字体族下拉(对照原版 ql-font)。 auto* fontBox = new EmptyAwareComboBox(tb); for (const auto& fo : kFontFamilies) fontBox->addItem(QString::fromUtf8(fo.label), QString::fromUtf8(fo.family)); tb->addWidget(fontBox); connect(fontBox, &QComboBox::currentIndexChanged, this, [this, fontBox](int) { const QString family = fontBox->currentData().toString(); QTextCharFormat f; f.setFontFamilies({family}); edit_->mergeCurrentCharFormat(f); }); } void DescriptionPanel::setDelta(const QJsonArray& ops) { if (ops.isEmpty()) return; deltaToDocument(ops, *edit_->document()); } void DescriptionPanel::setPlainText(const QString& text) { edit_->setPlainText(text); } QJsonArray DescriptionPanel::delta() const { return documentToDelta(*edit_->document()); } QString DescriptionPanel::plainText() const { return edit_->toPlainText(); } void DescriptionPanel::setSaveEnabled(bool on) { saveBtn_->setEnabled(on); } } // namespace geopro::app