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"