File indexing completed on 2024-05-05 05:21:41

0001 /*
0002   SPDX-FileCopyrightText: 2012-2024 Laurent Montel <montel@kde.org>
0003 
0004   SPDX-License-Identifier: LGPL-2.0-or-later
0005 
0006 */
0007 
0008 #include "tableactionmenu.h"
0009 #include "inserttabledialog.h"
0010 #include "tablecellformatdialog.h"
0011 #include "tableformatdialog.h"
0012 
0013 #include <KLocalizedString>
0014 #include <QAction>
0015 #include <QIcon>
0016 
0017 #include <QPointer>
0018 #include <QTextEdit>
0019 #include <QTextTable>
0020 
0021 namespace KPIMTextEdit
0022 {
0023 class TableActionMenuPrivate
0024 {
0025 public:
0026     explicit TableActionMenuPrivate(QTextEdit *edit, TableActionMenu *qq)
0027         : textEdit(edit)
0028         , q(qq)
0029     {
0030     }
0031 
0032     void _k_slotInsertRowBelow();
0033     void _k_slotInsertRowAbove();
0034     void _k_slotInsertColumnBefore();
0035     void _k_slotInsertColumnAfter();
0036 
0037     void _k_slotInsertTable();
0038 
0039     void _k_slotRemoveRowBelow();
0040     void _k_slotRemoveRowAbove();
0041     void _k_slotRemoveColumnBefore();
0042     void _k_slotRemoveColumnAfter();
0043     void _k_slotRemoveCellContents();
0044 
0045     void _k_slotMergeCell();
0046     void _k_slotMergeSelectedCells();
0047     void _k_slotTableFormat();
0048     void _k_slotTableCellFormat();
0049     void _k_slotSplitCell();
0050     void _k_updateActions(bool forceUpdate = false);
0051 
0052     QAction *actionInsertTable = nullptr;
0053 
0054     QAction *actionInsertRowBelow = nullptr;
0055     QAction *actionInsertRowAbove = nullptr;
0056 
0057     QAction *actionInsertColumnBefore = nullptr;
0058     QAction *actionInsertColumnAfter = nullptr;
0059 
0060     QAction *actionRemoveRowBelow = nullptr;
0061     QAction *actionRemoveRowAbove = nullptr;
0062 
0063     QAction *actionRemoveColumnBefore = nullptr;
0064     QAction *actionRemoveColumnAfter = nullptr;
0065 
0066     QAction *actionMergeCell = nullptr;
0067     QAction *actionMergeSelectedCells = nullptr;
0068     QAction *actionSplitCell = nullptr;
0069 
0070     QAction *actionTableFormat = nullptr;
0071     QAction *actionTableCellFormat = nullptr;
0072 
0073     QAction *actionRemoveCellContents = nullptr;
0074 
0075     QTextEdit *const textEdit;
0076     TableActionMenu *const q;
0077     bool richTextMode = false;
0078 };
0079 
0080 void TableActionMenuPrivate::_k_slotRemoveCellContents()
0081 {
0082     if (richTextMode) {
0083         QTextTable *table = textEdit->textCursor().currentTable();
0084         const QTextTableCell cell = table->cellAt(textEdit->textCursor());
0085         if (cell.isValid()) {
0086             const QTextCursor firstCursor = cell.firstCursorPosition();
0087             const QTextCursor endCursor = cell.lastCursorPosition();
0088             QTextCursor cursor = textEdit->textCursor();
0089             cursor.beginEditBlock();
0090             cursor.setPosition(firstCursor.position());
0091             cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, endCursor.position() - firstCursor.position());
0092             cursor.removeSelectedText();
0093             cursor.endEditBlock();
0094         }
0095     }
0096 }
0097 
0098 void TableActionMenuPrivate::_k_slotRemoveRowBelow()
0099 {
0100     if (richTextMode) {
0101         QTextTable *table = textEdit->textCursor().currentTable();
0102         if (table) {
0103             const QTextTableCell cell = table->cellAt(textEdit->textCursor());
0104             if (cell.row() < table->rows() - 1) {
0105                 table->removeRows(cell.row(), 1);
0106             }
0107         }
0108     }
0109 }
0110 
0111 void TableActionMenuPrivate::_k_slotRemoveRowAbove()
0112 {
0113     if (richTextMode) {
0114         QTextTable *table = textEdit->textCursor().currentTable();
0115         if (table) {
0116             const QTextTableCell cell = table->cellAt(textEdit->textCursor());
0117             if (cell.row() >= 1) {
0118                 table->removeRows(cell.row() - 1, 1);
0119             }
0120         }
0121     }
0122 }
0123 
0124 void TableActionMenuPrivate::_k_slotRemoveColumnBefore()
0125 {
0126     if (richTextMode) {
0127         QTextTable *table = textEdit->textCursor().currentTable();
0128         if (table) {
0129             const QTextTableCell cell = table->cellAt(textEdit->textCursor());
0130             if (cell.column() > 0) {
0131                 table->removeColumns(cell.column() - 1, 1);
0132             }
0133         }
0134     }
0135 }
0136 
0137 void TableActionMenuPrivate::_k_slotRemoveColumnAfter()
0138 {
0139     if (richTextMode) {
0140         QTextTable *table = textEdit->textCursor().currentTable();
0141         if (table) {
0142             const QTextTableCell cell = table->cellAt(textEdit->textCursor());
0143             if (cell.column() < table->columns() - 1) {
0144                 table->removeColumns(cell.column(), 1);
0145             }
0146         }
0147     }
0148 }
0149 
0150 void TableActionMenuPrivate::_k_slotInsertRowBelow()
0151 {
0152     if (richTextMode) {
0153         QTextTable *table = textEdit->textCursor().currentTable();
0154         if (table) {
0155             const QTextTableCell cell = table->cellAt(textEdit->textCursor());
0156             if (cell.row() < table->rows()) {
0157                 table->insertRows(cell.row() + 1, 1);
0158             } else {
0159                 table->appendRows(1);
0160             }
0161         }
0162     }
0163 }
0164 
0165 void TableActionMenuPrivate::_k_slotInsertRowAbove()
0166 {
0167     if (richTextMode) {
0168         QTextTable *table = textEdit->textCursor().currentTable();
0169         if (table) {
0170             const QTextTableCell cell = table->cellAt(textEdit->textCursor());
0171             table->insertRows(cell.row(), 1);
0172         }
0173     }
0174 }
0175 
0176 void TableActionMenuPrivate::_k_slotInsertColumnBefore()
0177 {
0178     if (richTextMode) {
0179         QTextTable *table = textEdit->textCursor().currentTable();
0180         if (table) {
0181             const QTextTableCell cell = table->cellAt(textEdit->textCursor());
0182             table->insertColumns(cell.column(), 1);
0183         }
0184     }
0185 }
0186 
0187 void TableActionMenuPrivate::_k_slotInsertColumnAfter()
0188 {
0189     if (richTextMode) {
0190         QTextTable *table = textEdit->textCursor().currentTable();
0191         if (table) {
0192             const QTextTableCell cell = table->cellAt(textEdit->textCursor());
0193             if (cell.column() < table->columns()) {
0194                 table->insertColumns(cell.column() + 1, 1);
0195             } else {
0196                 table->appendColumns(1);
0197             }
0198         }
0199     }
0200 }
0201 
0202 void TableActionMenuPrivate::_k_slotInsertTable()
0203 {
0204     if (richTextMode) {
0205         QPointer<InsertTableDialog> dialog = new InsertTableDialog(textEdit);
0206         if (dialog->exec()) {
0207             QTextCursor cursor = textEdit->textCursor();
0208             QTextTableFormat tableFormat;
0209             tableFormat.setBorder(dialog->border());
0210             const int numberOfColumns(dialog->columns());
0211             QList<QTextLength> constrains;
0212             constrains.reserve(numberOfColumns);
0213             const QTextLength::Type type = dialog->typeOfLength();
0214             const int length = dialog->length();
0215 
0216             const QTextLength textlength(type, length / numberOfColumns);
0217             for (int i = 0; i < numberOfColumns; ++i) {
0218                 constrains.append(textlength);
0219             }
0220             tableFormat.setColumnWidthConstraints(constrains);
0221             tableFormat.setAlignment(Qt::AlignLeft);
0222             QTextTable *table = cursor.insertTable(dialog->rows(), numberOfColumns);
0223             table->setFormat(tableFormat);
0224         }
0225         delete dialog;
0226     }
0227 }
0228 
0229 void TableActionMenuPrivate::_k_slotMergeCell()
0230 {
0231     if (richTextMode) {
0232         QTextTable *table = textEdit->textCursor().currentTable();
0233         if (table) {
0234             const QTextTableCell cell = table->cellAt(textEdit->textCursor());
0235             table->mergeCells(cell.row(), cell.column(), 1, cell.columnSpan() + 1);
0236         }
0237     }
0238 }
0239 
0240 void TableActionMenuPrivate::_k_slotMergeSelectedCells()
0241 {
0242     if (richTextMode) {
0243         QTextTable *table = textEdit->textCursor().currentTable();
0244         if (table) {
0245             table->mergeCells(textEdit->textCursor());
0246         }
0247     }
0248 }
0249 
0250 void TableActionMenuPrivate::_k_slotTableFormat()
0251 {
0252     if (richTextMode) {
0253         QTextTable *table = textEdit->textCursor().currentTable();
0254         if (table) {
0255             QPointer<TableFormatDialog> dialog = new TableFormatDialog(textEdit);
0256             const int numberOfColumn(table->columns());
0257             const int numberOfRow(table->rows());
0258             dialog->setColumns(numberOfColumn);
0259             dialog->setRows(numberOfRow);
0260             QTextTableFormat tableFormat = table->format();
0261             dialog->setBorder(tableFormat.border());
0262             dialog->setSpacing(tableFormat.cellSpacing());
0263             dialog->setPadding(tableFormat.cellPadding());
0264             dialog->setAlignment(tableFormat.alignment());
0265             if (tableFormat.hasProperty(QTextFormat::BackgroundBrush)) {
0266                 dialog->setTableBackgroundColor(tableFormat.background().color());
0267             }
0268             QList<QTextLength> constrains = tableFormat.columnWidthConstraints();
0269             if (!constrains.isEmpty()) {
0270                 dialog->setTypeOfLength(constrains.at(0).type());
0271                 dialog->setLength(constrains.at(0).rawValue() * numberOfColumn);
0272             }
0273 
0274             if (dialog->exec()) {
0275                 const int newNumberOfColumns(dialog->columns());
0276                 if ((newNumberOfColumns != numberOfColumn) || (dialog->rows() != numberOfRow)) {
0277                     table->resize(dialog->rows(), newNumberOfColumns);
0278                 }
0279                 tableFormat.setBorder(dialog->border());
0280                 tableFormat.setCellPadding(dialog->padding());
0281                 tableFormat.setCellSpacing(dialog->spacing());
0282                 tableFormat.setAlignment(dialog->alignment());
0283 
0284                 QList<QTextLength> constrainsText;
0285                 constrainsText.reserve(newNumberOfColumns);
0286                 const QTextLength::Type type = dialog->typeOfLength();
0287                 const int length = dialog->length();
0288 
0289                 const QTextLength textlength(type, length / newNumberOfColumns);
0290                 for (int i = 0; i < newNumberOfColumns; ++i) {
0291                     constrainsText.append(textlength);
0292                 }
0293                 tableFormat.setColumnWidthConstraints(constrainsText);
0294                 const QColor tableBackgroundColor = dialog->tableBackgroundColor();
0295                 if (dialog->useBackgroundColor()) {
0296                     if (tableBackgroundColor.isValid()) {
0297                         tableFormat.setBackground(tableBackgroundColor);
0298                     }
0299                 } else {
0300                     tableFormat.clearBackground();
0301                 }
0302                 table->setFormat(tableFormat);
0303             }
0304             delete dialog;
0305         }
0306     }
0307 }
0308 
0309 void TableActionMenuPrivate::_k_slotTableCellFormat()
0310 {
0311     if (richTextMode) {
0312         QTextTable *table = textEdit->textCursor().currentTable();
0313         if (table) {
0314             QTextTableCell cell = table->cellAt(textEdit->textCursor());
0315             QPointer<TableCellFormatDialog> dialog = new TableCellFormatDialog(textEdit);
0316             QTextTableCellFormat format = cell.format().toTableCellFormat();
0317             if (format.hasProperty(QTextFormat::BackgroundBrush)) {
0318                 dialog->setTableCellBackgroundColor(format.background().color());
0319             }
0320             dialog->setVerticalAlignment(format.verticalAlignment());
0321             if (dialog->exec()) {
0322                 if (dialog->useBackgroundColor()) {
0323                     const QColor tableCellColor = dialog->tableCellBackgroundColor();
0324                     if (tableCellColor.isValid()) {
0325                         format.setBackground(tableCellColor);
0326                     }
0327                 } else {
0328                     format.clearBackground();
0329                 }
0330                 format.setVerticalAlignment(dialog->verticalAlignment());
0331                 cell.setFormat(format);
0332             }
0333             delete dialog;
0334         }
0335     }
0336 }
0337 
0338 void TableActionMenuPrivate::_k_slotSplitCell()
0339 {
0340     if (richTextMode) {
0341         QTextTable *table = textEdit->textCursor().currentTable();
0342         if (table) {
0343             const QTextTableCell cell = table->cellAt(textEdit->textCursor());
0344             if (cell.columnSpan() > 1 || cell.rowSpan() > 1) {
0345                 table->splitCell(cell.row(), cell.column(), qMax(1, cell.rowSpan() - 1), qMax(1, cell.columnSpan() - 1));
0346                 _k_updateActions();
0347             }
0348         }
0349     }
0350 }
0351 
0352 void TableActionMenuPrivate::_k_updateActions(bool forceUpdate)
0353 {
0354     if ((richTextMode) || forceUpdate) {
0355         QTextTable *table = textEdit->textCursor().currentTable();
0356         const bool isTable = (table != nullptr);
0357         actionInsertRowBelow->setEnabled(isTable);
0358         actionInsertRowAbove->setEnabled(isTable);
0359 
0360         actionInsertColumnBefore->setEnabled(isTable);
0361         actionInsertColumnAfter->setEnabled(isTable);
0362 
0363         actionRemoveRowBelow->setEnabled(isTable);
0364         actionRemoveRowAbove->setEnabled(isTable);
0365 
0366         actionRemoveColumnBefore->setEnabled(isTable);
0367         actionRemoveColumnAfter->setEnabled(isTable);
0368 
0369         if (table) {
0370             const QTextTableCell cell = table->cellAt(textEdit->textCursor());
0371 
0372             int firstRow = -1;
0373             int numRows = -1;
0374             int firstColumn = -1;
0375             int numColumns = -1;
0376             textEdit->textCursor().selectedTableCells(&firstRow, &numRows, &firstColumn, &numColumns);
0377             const bool hasSelectedTableCell = (firstRow != -1) && (numRows != -1) && (firstColumn != -1) && (numColumns != -1);
0378             if (cell.column() > table->columns() - 2) {
0379                 actionMergeCell->setEnabled(false);
0380             } else {
0381                 actionMergeCell->setEnabled(true);
0382             }
0383             if (cell.columnSpan() > 1 || cell.rowSpan() > 1) {
0384                 actionSplitCell->setEnabled(true);
0385             } else {
0386                 actionSplitCell->setEnabled(false);
0387             }
0388             actionTableCellFormat->setEnabled(true);
0389             actionMergeSelectedCells->setEnabled(hasSelectedTableCell);
0390         } else {
0391             actionSplitCell->setEnabled(false);
0392             actionMergeCell->setEnabled(false);
0393             actionMergeSelectedCells->setEnabled(false);
0394         }
0395         actionTableFormat->setEnabled(isTable);
0396         actionTableCellFormat->setEnabled(isTable);
0397         actionRemoveCellContents->setEnabled(isTable);
0398     }
0399 }
0400 
0401 TableActionMenu::TableActionMenu(QTextEdit *textEdit)
0402     : KActionMenu(textEdit)
0403     , d(new TableActionMenuPrivate(textEdit, this))
0404 {
0405     auto insertMenu = new KActionMenu(i18n("Insert"), this);
0406     addAction(insertMenu);
0407 
0408     d->actionInsertTable = new QAction(QIcon::fromTheme(QStringLiteral("insert-table")), i18n("Table..."), this);
0409     d->actionInsertTable->setObjectName(QLatin1StringView("insert_new_table"));
0410     insertMenu->addAction(d->actionInsertTable);
0411     connect(d->actionInsertTable, &QAction::triggered, this, [this]() {
0412         d->_k_slotInsertTable();
0413     });
0414 
0415     insertMenu->addSeparator();
0416     d->actionInsertRowBelow = new QAction(QIcon::fromTheme(QStringLiteral("edit-table-insert-row-below")), i18n("Row Below"), this);
0417     insertMenu->addAction(d->actionInsertRowBelow);
0418     d->actionInsertRowBelow->setObjectName(QLatin1StringView("insert_row_below"));
0419     connect(d->actionInsertRowBelow, &QAction::triggered, this, [this]() {
0420         d->_k_slotInsertRowBelow();
0421     });
0422 
0423     d->actionInsertRowAbove = new QAction(QIcon::fromTheme(QStringLiteral("edit-table-insert-row-above")), i18n("Row Above"), this);
0424     insertMenu->addAction(d->actionInsertRowAbove);
0425     d->actionInsertRowAbove->setObjectName(QLatin1StringView("insert_row_above"));
0426     connect(d->actionInsertRowAbove, &QAction::triggered, this, [this]() {
0427         d->_k_slotInsertRowAbove();
0428     });
0429 
0430     insertMenu->addSeparator();
0431     d->actionInsertColumnBefore = new QAction(QIcon::fromTheme(QStringLiteral("edit-table-insert-column-left")), i18n("Column Before"), this);
0432     insertMenu->addAction(d->actionInsertColumnBefore);
0433     d->actionInsertColumnBefore->setObjectName(QLatin1StringView("insert_column_before"));
0434 
0435     connect(d->actionInsertColumnBefore, &QAction::triggered, this, [this]() {
0436         d->_k_slotInsertColumnBefore();
0437     });
0438 
0439     d->actionInsertColumnAfter = new QAction(QIcon::fromTheme(QStringLiteral("edit-table-insert-column-right")), i18n("Column After"), this);
0440     insertMenu->addAction(d->actionInsertColumnAfter);
0441     d->actionInsertColumnAfter->setObjectName(QLatin1StringView("insert_column_after"));
0442     connect(d->actionInsertColumnAfter, &QAction::triggered, this, [this]() {
0443         d->_k_slotInsertColumnAfter();
0444     });
0445 
0446     auto removeMenu = new KActionMenu(i18n("Delete"), this);
0447     addAction(removeMenu);
0448 
0449     d->actionRemoveRowBelow = new QAction(i18n("Row Below"), this);
0450     removeMenu->addAction(d->actionRemoveRowBelow);
0451     d->actionRemoveRowBelow->setObjectName(QLatin1StringView("remove_row_below"));
0452     connect(d->actionRemoveRowBelow, &QAction::triggered, this, [this]() {
0453         d->_k_slotRemoveRowBelow();
0454     });
0455 
0456     d->actionRemoveRowAbove = new QAction(i18n("Row Above"), this);
0457     removeMenu->addAction(d->actionRemoveRowAbove);
0458     d->actionRemoveRowAbove->setObjectName(QLatin1StringView("remove_row_above"));
0459     connect(d->actionRemoveRowAbove, &QAction::triggered, this, [this]() {
0460         d->_k_slotRemoveRowAbove();
0461     });
0462 
0463     removeMenu->addSeparator();
0464     d->actionRemoveColumnBefore = new QAction(i18n("Column Before"), this);
0465     removeMenu->addAction(d->actionRemoveColumnBefore);
0466     d->actionRemoveColumnBefore->setObjectName(QLatin1StringView("remove_column_before"));
0467 
0468     connect(d->actionRemoveColumnBefore, &QAction::triggered, this, [this]() {
0469         d->_k_slotRemoveColumnBefore();
0470     });
0471 
0472     d->actionRemoveColumnAfter = new QAction(i18n("Column After"), this);
0473     removeMenu->addAction(d->actionRemoveColumnAfter);
0474     d->actionRemoveColumnAfter->setObjectName(QLatin1StringView("remove_column_after"));
0475     connect(d->actionRemoveColumnAfter, &QAction::triggered, this, [this]() {
0476         d->_k_slotRemoveColumnAfter();
0477     });
0478 
0479     removeMenu->addSeparator();
0480     d->actionRemoveCellContents = new QAction(i18n("Cell Contents"), this);
0481     removeMenu->addAction(d->actionRemoveCellContents);
0482     d->actionRemoveCellContents->setObjectName(QLatin1StringView("remove_cell_contents"));
0483     connect(d->actionRemoveCellContents, &QAction::triggered, this, [this]() {
0484         d->_k_slotRemoveCellContents();
0485     });
0486 
0487     addSeparator();
0488 
0489     d->actionMergeCell = new QAction(QIcon::fromTheme(QStringLiteral("edit-table-cell-merge")), i18n("Join With Cell to the Right"), this);
0490     d->actionMergeCell->setObjectName(QLatin1StringView("join_cell_to_the_right"));
0491     connect(d->actionMergeCell, &QAction::triggered, this, [this]() {
0492         d->_k_slotMergeCell();
0493     });
0494     addAction(d->actionMergeCell);
0495 
0496     d->actionMergeSelectedCells = new QAction(i18n("Join Selected Cells"), this);
0497     d->actionMergeSelectedCells->setObjectName(QLatin1StringView("join_cell_selected_cells"));
0498     connect(d->actionMergeSelectedCells, &QAction::triggered, this, [this]() {
0499         d->_k_slotMergeSelectedCells();
0500     });
0501     addAction(d->actionMergeSelectedCells);
0502 
0503     d->actionSplitCell = new QAction(QIcon::fromTheme(QStringLiteral("edit-table-cell-split")), i18n("Split cells"), this);
0504     d->actionSplitCell->setObjectName(QLatin1StringView("split_cells"));
0505     connect(d->actionSplitCell, &QAction::triggered, this, [this]() {
0506         d->_k_slotSplitCell();
0507     });
0508     addAction(d->actionSplitCell);
0509 
0510     addSeparator();
0511 
0512     d->actionTableFormat = new QAction(i18n("Table Format..."), this);
0513     d->actionTableFormat->setObjectName(QLatin1StringView("table_format"));
0514     connect(d->actionTableFormat, &QAction::triggered, this, [this]() {
0515         d->_k_slotTableFormat();
0516     });
0517     addAction(d->actionTableFormat);
0518 
0519     d->actionTableCellFormat = new QAction(i18n("Table Cell Format..."), this);
0520     d->actionTableCellFormat->setObjectName(QLatin1StringView("table_cell_format"));
0521     connect(d->actionTableCellFormat, &QAction::triggered, this, [this]() {
0522         d->_k_slotTableCellFormat();
0523     });
0524     addAction(d->actionTableCellFormat);
0525 
0526     connect(textEdit, &QTextEdit::cursorPositionChanged, this, [this]() {
0527         d->_k_updateActions(false);
0528     });
0529     d->_k_updateActions(true);
0530 }
0531 
0532 TableActionMenu::~TableActionMenu() = default;
0533 
0534 void TableActionMenu::setRichTextMode(bool richTextMode)
0535 {
0536     d->richTextMode = richTextMode;
0537 }
0538 }
0539 
0540 #include "moc_tableactionmenu.cpp"