#include "richtexteditor.h" #include #include #include #include #include #include #include #include #include #include "../../thirdparty/tags/include/tags_line_edit.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // ===================== RichTextEditor ===================== RichTextEditor::RichTextEditor(QWidget *parent) : QTextEdit(parent) { setAcceptRichText(true); setPlaceholderText("Write your message here..."); setMouseTracking(true); } void RichTextEditor::setupToolbar(QVBoxLayout *layout) { m_toolbar = new QToolBar("Formatting"); m_toolbar->setIconSize(QSize(16, 16)); m_toolbar->setStyleSheet( "QToolBar { background: #f5f5f7; border: 1px solid #d1d1d6; border-radius: 4px; spacing: 2px; padding: 2px; }\n" "QToolButton { padding: 4px 6px; border-radius: 3px; }\n" "QToolButton:hover { background: #e0e0e0; }\n" "QToolButton:checked { background: #bbdefb; }\n" ); // Font family combo m_fontCombo = new QFontComboBox(); m_fontCombo->setFixedWidth(150); connect(m_fontCombo, &QFontComboBox::currentFontChanged, this, &RichTextEditor::onFontChanged); m_toolbar->addWidget(m_fontCombo); // Font size spin m_fontSizeSpin = new QSpinBox(); m_fontSizeSpin->setRange(8, 72); m_fontSizeSpin->setValue(11); m_fontSizeSpin->setFixedWidth(70); m_fontSizeSpin->setFixedHeight(24); connect(m_fontSizeSpin, QOverload::of(&QSpinBox::valueChanged), this, &RichTextEditor::onFontSizeChanged); m_toolbar->addWidget(m_fontSizeSpin); m_toolbar->addSeparator(); // Bold / Italic / Underline QAction *boldAct = m_toolbar->addAction("B"); boldAct->setCheckable(true); boldAct->setIcon(QIcon(QStringLiteral(":/icons/resources/icons/SVG/Bold/Text Formatting/Text Bold.svg"))); QFont boldFont = boldAct->font(); boldFont.setBold(true); boldAct->setFont(boldFont); connect(boldAct, &QAction::triggered, this, &RichTextEditor::onBold); QAction *italicAct = m_toolbar->addAction("I"); italicAct->setCheckable(true); italicAct->setIcon(QIcon(QStringLiteral(":/icons/resources/icons/SVG/Outline/Text Formatting/Text Italic.svg"))); QFont italicFont = italicAct->font(); italicFont.setItalic(true); italicAct->setFont(italicFont); connect(italicAct, &QAction::triggered, this, &RichTextEditor::onItalic); QAction *underlineAct = m_toolbar->addAction("U"); underlineAct->setCheckable(true); underlineAct->setIcon(QIcon(QStringLiteral(":/icons/resources/icons/SVG/Outline/Text Formatting/Text Underline.svg"))); QFont uFont = underlineAct->font(); uFont.setUnderline(true); underlineAct->setFont(uFont); connect(underlineAct, &QAction::triggered, this, &RichTextEditor::onUnderline); m_toolbar->addSeparator(); // Alignment QAction *alignLeft = m_toolbar->addAction("L"); connect(alignLeft, &QAction::triggered, this, &RichTextEditor::onAlignLeft); QAction *alignCenter = m_toolbar->addAction("C"); connect(alignCenter, &QAction::triggered, this, &RichTextEditor::onAlignCenter); QAction *alignRight = m_toolbar->addAction("R"); connect(alignRight, &QAction::triggered, this, &RichTextEditor::onAlignRight); QAction *alignJustify = m_toolbar->addAction("J"); connect(alignJustify, &QAction::triggered, this, &RichTextEditor::onAlignJustify); m_toolbar->addSeparator(); // Lists QAction *bulletAct = m_toolbar->addAction("Bullets"); connect(bulletAct, &QAction::triggered, this, &RichTextEditor::onBulletList); QAction *numAct = m_toolbar->addAction("1. List"); connect(numAct, &QAction::triggered, this, &RichTextEditor::onNumberedList); m_toolbar->addSeparator(); // Indent / Outdent QAction *indentAct = m_toolbar->addAction("Indent"); connect(indentAct, &QAction::triggered, this, &RichTextEditor::onIndent); QAction *outdentAct = m_toolbar->addAction("Outdent"); connect(outdentAct, &QAction::triggered, this, &RichTextEditor::onOutdent); m_toolbar->addSeparator(); // Insert image QAction *imgAct = m_toolbar->addAction("Img"); connect(imgAct, &QAction::triggered, this, &RichTextEditor::onInsertImage); // Insert table QAction *tableAct = m_toolbar->addAction("Tbl"); connect(tableAct, &QAction::triggered, this, &RichTextEditor::onInsertTable); // Signature button m_signatureButton = new QToolButton(); m_signatureButton->setText("Signature"); m_signatureButton->setToolButtonStyle(Qt::ToolButtonTextOnly); m_signatureButton->setPopupMode(QToolButton::MenuButtonPopup); m_signatureMenu = new QMenu(m_signatureButton); m_signatureButton->setMenu(m_signatureMenu); m_signatureButton->setStyleSheet( "QToolButton { background: #f5f5f7; border: 1px solid #d1d1d6; border-radius: 3px; padding: 4px 6px; }\n" "QToolButton:hover { background: #e0e0e0; }\n" "QToolButton::menu-button { border-left: 1px solid rgba(0,0,0,0.1); width: 12px; }\n" ); connect(m_signatureButton, &QToolButton::clicked, this, &RichTextEditor::signatureClicked); m_toolbar->addWidget(m_signatureButton); QAction *editSigAct = m_signatureMenu->addAction(tr("Editar firmas")); connect(editSigAct, &QAction::triggered, this, &RichTextEditor::signatureEditRequested); m_toolbar->addSeparator(); layout->addWidget(m_toolbar); } void RichTextEditor::onBold() { QTextCharFormat fmt; fmt.setFontWeight(textCursor().charFormat().fontWeight() == QFont::Bold ? QFont::Normal : QFont::Bold); mergeCurrentCharFormat(fmt); setFocus(); } void RichTextEditor::onItalic() { QTextCharFormat fmt; fmt.setFontItalic(!textCursor().charFormat().fontItalic()); mergeCurrentCharFormat(fmt); setFocus(); } void RichTextEditor::onUnderline() { QTextCharFormat fmt; fmt.setFontUnderline(!textCursor().charFormat().fontUnderline()); mergeCurrentCharFormat(fmt); setFocus(); } void RichTextEditor::onBulletList() { QTextCursor cursor = textCursor(); QTextList *list = cursor.currentList(); if (list && list->format().style() == QTextListFormat::ListDisc) { // Remove list QTextBlockFormat bfmt; bfmt.setIndent(0); cursor.setBlockFormat(bfmt); list->remove(cursor.block()); } else { QTextListFormat listFormat; listFormat.setStyle(QTextListFormat::ListDisc); cursor.createList(listFormat); } } void RichTextEditor::onNumberedList() { QTextCursor cursor = textCursor(); QTextList *list = cursor.currentList(); if (list && list->format().style() == QTextListFormat::ListDecimal) { QTextBlockFormat bfmt; bfmt.setIndent(0); cursor.setBlockFormat(bfmt); list->remove(cursor.block()); } else { QTextListFormat listFormat; listFormat.setStyle(QTextListFormat::ListDecimal); cursor.createList(listFormat); } } void RichTextEditor::onIndent() { QTextCursor cursor = textCursor(); QTextBlockFormat bfmt = cursor.blockFormat(); bfmt.setIndent(bfmt.indent() + 1); cursor.setBlockFormat(bfmt); } void RichTextEditor::onOutdent() { QTextCursor cursor = textCursor(); QTextBlockFormat bfmt = cursor.blockFormat(); int indent = bfmt.indent(); if (indent > 0) { bfmt.setIndent(indent - 1); cursor.setBlockFormat(bfmt); } } void RichTextEditor::onAlignLeft() { QTextCursor cursor = textCursor(); QTextBlockFormat bfmt = cursor.blockFormat(); bfmt.setAlignment(Qt::AlignLeft); cursor.setBlockFormat(bfmt); } void RichTextEditor::onAlignCenter() { QTextCursor cursor = textCursor(); QTextBlockFormat bfmt = cursor.blockFormat(); bfmt.setAlignment(Qt::AlignCenter); cursor.setBlockFormat(bfmt); } void RichTextEditor::onAlignRight() { QTextCursor cursor = textCursor(); QTextBlockFormat bfmt = cursor.blockFormat(); bfmt.setAlignment(Qt::AlignRight); cursor.setBlockFormat(bfmt); } void RichTextEditor::onAlignJustify() { QTextCursor cursor = textCursor(); QTextBlockFormat bfmt = cursor.blockFormat(); bfmt.setAlignment(Qt::AlignJustify); cursor.setBlockFormat(bfmt); } void RichTextEditor::onFontChanged(const QFont &font) { QTextCharFormat fmt; fmt.setFontFamilies({font.family()}); mergeCurrentCharFormat(fmt); } void RichTextEditor::onFontSizeChanged(int size) { QTextCharFormat fmt; fmt.setFontPointSize(size); mergeCurrentCharFormat(fmt); } void RichTextEditor::onInsertImage() { QString filePath = QFileDialog::getOpenFileName(this, "Insert Image", QString(), "Images (*.png *.jpg *.jpeg *.gif *.bmp)"); if (filePath.isEmpty()) return; QTextCursor cursor = textCursor(); QTextImageFormat imgFmt; imgFmt.setName(filePath); // Scale down if too large QPixmap pm(filePath); if (pm.width() > 600) { imgFmt.setWidth(600); imgFmt.setHeight(pm.height() * 600 / pm.width()); } cursor.insertImage(imgFmt); } void RichTextEditor::onInsertTable() { bool ok; int rows = QInputDialog::getInt(this, "Table Rows", "Rows:", 3, 1, 50, 1, &ok); if (!ok) return; int cols = QInputDialog::getInt(this, "Table Columns", "Columns:", 3, 1, 20, 1, &ok); if (!ok) return; QTextCursor cursor = textCursor(); QTextTableFormat tableFmt; tableFmt.setBorder(1); tableFmt.setBorderStyle(QTextFrameFormat::BorderStyle_Solid); tableFmt.setCellPadding(4); tableFmt.setCellSpacing(0); tableFmt.setWidth(QTextLength(QTextLength::PercentageLength, 100)); cursor.insertTable(rows, cols, tableFmt); } /* ===================== Mouse events for image/table resizing ===================== */ void RichTextEditor::mousePressEvent(QMouseEvent *event) { if (event->button() == Qt::LeftButton) { QPoint pos = event->pos(); QTextCursor cursor = cursorForPosition(pos); // Image if (cursor.charFormat().isImageFormat()) { qDebug() << "Mouse press on image"; m_resizingImage = true; m_resizeStartPos = pos; QTextImageFormat imgFormat = cursor.charFormat().toImageFormat(); m_imageStartSize = QSize(imgFormat.width(), imgFormat.height()); if (m_imageStartSize.isEmpty()) { QPixmap pm(imgFormat.name()); if (!pm.isNull()) m_imageStartSize = pm.size(); } // Change cursor to indicate resizing setCursor(Qt::SizeAllCursor); event->accept(); return; } // Table QTextTable *table = cursor.currentTable(); if (table) { qDebug() << "Mouse press on table"; m_resizingTable = true; m_tableStartPos = pos; m_currentTable = table; QTextTableFormat fmt = table->format(); m_tableStartSize = QSizeF(fmt.width().rawValue(), fmt.height().rawValue()); setCursor(Qt::SizeAllCursor); event->accept(); return; } } QTextEdit::mousePressEvent(event); } void RichTextEditor::mouseMoveEvent(QMouseEvent *event) { if (m_resizingImage) { qDebug() << "Mouse move resizing image"; QPoint delta = event->pos() - m_resizeStartPos; int newWidth = qMax(20, m_imageStartSize.width() + delta.x()); int newHeight = qMax(20, m_imageStartSize.height() + delta.y()); QTextCursor cursor = cursorForPosition(m_resizeStartPos); if (cursor.charFormat().isImageFormat()) { QTextImageFormat fmt = cursor.charFormat().toImageFormat(); fmt.setWidth(newWidth); fmt.setHeight(newHeight); cursor.mergeCharFormat(fmt); setTextCursor(cursor); } event->accept(); return; } if (m_resizingTable) { qDebug() << "Mouse move resizing table"; QPoint delta = event->pos() - m_tableStartPos; qreal newWidth = qMax(10.0, m_tableStartSize.width() + delta.x()); qreal newHeight = qMax(10.0, m_tableStartSize.height() + delta.y()); QTextTableFormat fmt = m_currentTable->format(); fmt.setWidth(QTextLength(QTextLength::FixedLength, newWidth)); fmt.setHeight(QTextLength(QTextLength::FixedLength, newHeight)); m_currentTable->setFormat(fmt); event->accept(); return; } QTextEdit::mouseMoveEvent(event); } void RichTextEditor::mouseReleaseEvent(QMouseEvent *event) { if (event->button() == Qt::LeftButton) { if (m_resizingImage) { qDebug() << "Mouse release image resize"; m_resizingImage = false; setCursor(Qt::ArrowCursor); event->accept(); return; } if (m_resizingTable) { qDebug() << "Mouse release table resize"; m_resizingTable = false; setCursor(Qt::ArrowCursor); event->accept(); return; } } QTextEdit::mouseReleaseEvent(event); } void RichTextEditor::contextMenuEvent(QContextMenuEvent *event) { QPoint pos = event->pos(); QTextCursor cursor = cursorForPosition(pos); QTextTable *table = cursor.currentTable(); if (table) { showTableContextMenu(pos); event->accept(); return; } QTextEdit::contextMenuEvent(event); } /* ===================== Helper functions ===================== */ void RichTextEditor::showTableContextMenu(const QPoint &pos) { if (!m_tableContextMenu) { m_tableContextMenu = new QMenu(this); m_tableContextMenu->addAction(tr("Insert row above"), this, &RichTextEditor::addTableRow); m_tableContextMenu->addAction(tr("Insert row below"), this, &RichTextEditor::addTableRow); m_tableContextMenu->addAction(tr("Insert column left"), this, &RichTextEditor::addTableColumn); m_tableContextMenu->addAction(tr("Insert column right"), this, &RichTextEditor::addTableColumn); m_tableContextMenu->addSeparator(); m_tableContextMenu->addAction(tr("Delete selected rows"), this, &RichTextEditor::removeTableRow); m_tableContextMenu->addAction(tr("Delete selected columns"), this, &RichTextEditor::removeTableColumn); m_tableContextMenu->addSeparator(); m_tableContextMenu->addAction(tr("Border..."), this, &RichTextEditor::modifyTableBorder); } m_tableContextMenu->exec(mapToGlobal(pos)); } void RichTextEditor::addTableRow() { if (!m_currentTable) return; QTextCursor cursor = textCursor(); QTextTable *table = cursor.currentTable(); if (!table) return; QTextTableCell cell = table->cellAt(cursor.position()); int row = cell.row(); table->insertRows(row, 1); } void RichTextEditor::removeTableRow() { if (!m_currentTable) return; QTextCursor cursor = textCursor(); QTextTable *table = cursor.currentTable(); if (!table) return; QTextTableCell cell = table->cellAt(cursor.position()); int row = cell.row(); table->removeRows(row, 1); } void RichTextEditor::addTableColumn() { if (!m_currentTable) return; QTextCursor cursor = textCursor(); QTextTable *table = cursor.currentTable(); if (!table) return; QTextTableCell cell = table->cellAt(cursor.position()); int col = cell.column(); table->insertColumns(col, 1); } void RichTextEditor::removeTableColumn() { if (!m_currentTable) return; QTextCursor cursor = textCursor(); QTextTable *table = cursor.currentTable(); if (!table) return; QTextTableCell cell = table->cellAt(cursor.position()); int col = cell.column(); table->removeColumns(col, 1); } void RichTextEditor::modifyTableBorder() { if (!m_currentTable) return; bool ok; int border = QInputDialog::getInt(this, tr("Table Border"), tr("Border width (px):"), m_currentTable->format().border(), 0, 20, 1, &ok); if (!ok) return; QTextTableFormat fmt = m_currentTable->format(); fmt.setBorder(border); m_currentTable->setFormat(fmt); }