KPimTextEdit

tableactionmenu.cpp
1/*
2 SPDX-FileCopyrightText: 2012-2024 Laurent Montel <montel@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5
6*/
7
8#include "tableactionmenu.h"
9using namespace Qt::Literals::StringLiterals;
10
11#include "inserttabledialog.h"
12#include "tablecellformatdialog.h"
13#include "tableformatdialog.h"
14
15#include <KLocalizedString>
16#include <QAction>
17#include <QIcon>
18
19#include <QPointer>
20#include <QTextEdit>
21#include <QTextTable>
22
23namespace KPIMTextEdit
24{
25class TableActionMenuPrivate
26{
27public:
28 explicit TableActionMenuPrivate(QTextEdit *edit, TableActionMenu *qq)
29 : textEdit(edit)
30 , q(qq)
31 {
32 }
33
34 void _k_slotInsertRowBelow();
35 void _k_slotInsertRowAbove();
36 void _k_slotInsertColumnBefore();
37 void _k_slotInsertColumnAfter();
38
39 void _k_slotInsertTable();
40
41 void _k_slotRemoveRowBelow();
42 void _k_slotRemoveRowAbove();
43 void _k_slotRemoveColumnBefore();
44 void _k_slotRemoveColumnAfter();
45 void _k_slotRemoveCellContents();
46
47 void _k_slotMergeCell();
48 void _k_slotMergeSelectedCells();
49 void _k_slotTableFormat();
50 void _k_slotTableCellFormat();
51 void _k_slotSplitCell();
52 void _k_updateActions(bool forceUpdate = false);
53
54 QAction *actionInsertTable = nullptr;
55
56 QAction *actionInsertRowBelow = nullptr;
57 QAction *actionInsertRowAbove = nullptr;
58
59 QAction *actionInsertColumnBefore = nullptr;
60 QAction *actionInsertColumnAfter = nullptr;
61
62 QAction *actionRemoveRowBelow = nullptr;
63 QAction *actionRemoveRowAbove = nullptr;
64
65 QAction *actionRemoveColumnBefore = nullptr;
66 QAction *actionRemoveColumnAfter = nullptr;
67
68 QAction *actionMergeCell = nullptr;
69 QAction *actionMergeSelectedCells = nullptr;
70 QAction *actionSplitCell = nullptr;
71
72 QAction *actionTableFormat = nullptr;
73 QAction *actionTableCellFormat = nullptr;
74
75 QAction *actionRemoveCellContents = nullptr;
76
77 QTextEdit *const textEdit;
78 TableActionMenu *const q;
79 bool richTextMode = false;
80};
81
82void TableActionMenuPrivate::_k_slotRemoveCellContents()
83{
84 if (richTextMode) {
85 QTextTable *table = textEdit->textCursor().currentTable();
86 const QTextTableCell cell = table->cellAt(textEdit->textCursor());
87 if (cell.isValid()) {
88 const QTextCursor firstCursor = cell.firstCursorPosition();
89 const QTextCursor endCursor = cell.lastCursorPosition();
90 QTextCursor cursor = textEdit->textCursor();
91 cursor.beginEditBlock();
92 cursor.setPosition(firstCursor.position());
94 cursor.removeSelectedText();
95 cursor.endEditBlock();
96 }
97 }
98}
99
100void TableActionMenuPrivate::_k_slotRemoveRowBelow()
101{
102 if (richTextMode) {
103 QTextTable *table = textEdit->textCursor().currentTable();
104 if (table) {
105 const QTextTableCell cell = table->cellAt(textEdit->textCursor());
106 if (cell.row() < table->rows() - 1) {
107 table->removeRows(cell.row(), 1);
108 }
109 }
110 }
111}
112
113void TableActionMenuPrivate::_k_slotRemoveRowAbove()
114{
115 if (richTextMode) {
116 QTextTable *table = textEdit->textCursor().currentTable();
117 if (table) {
118 const QTextTableCell cell = table->cellAt(textEdit->textCursor());
119 if (cell.row() >= 1) {
120 table->removeRows(cell.row() - 1, 1);
121 }
122 }
123 }
124}
125
126void TableActionMenuPrivate::_k_slotRemoveColumnBefore()
127{
128 if (richTextMode) {
129 QTextTable *table = textEdit->textCursor().currentTable();
130 if (table) {
131 const QTextTableCell cell = table->cellAt(textEdit->textCursor());
132 if (cell.column() > 0) {
133 table->removeColumns(cell.column() - 1, 1);
134 }
135 }
136 }
137}
138
139void TableActionMenuPrivate::_k_slotRemoveColumnAfter()
140{
141 if (richTextMode) {
142 QTextTable *table = textEdit->textCursor().currentTable();
143 if (table) {
144 const QTextTableCell cell = table->cellAt(textEdit->textCursor());
145 if (cell.column() < table->columns() - 1) {
146 table->removeColumns(cell.column(), 1);
147 }
148 }
149 }
150}
151
152void TableActionMenuPrivate::_k_slotInsertRowBelow()
153{
154 if (richTextMode) {
155 QTextTable *table = textEdit->textCursor().currentTable();
156 if (table) {
157 const QTextTableCell cell = table->cellAt(textEdit->textCursor());
158 if (cell.row() < table->rows()) {
159 table->insertRows(cell.row() + 1, 1);
160 } else {
161 table->appendRows(1);
162 }
163 }
164 }
165}
166
167void TableActionMenuPrivate::_k_slotInsertRowAbove()
168{
169 if (richTextMode) {
170 QTextTable *table = textEdit->textCursor().currentTable();
171 if (table) {
172 const QTextTableCell cell = table->cellAt(textEdit->textCursor());
173 table->insertRows(cell.row(), 1);
174 }
175 }
176}
177
178void TableActionMenuPrivate::_k_slotInsertColumnBefore()
179{
180 if (richTextMode) {
181 QTextTable *table = textEdit->textCursor().currentTable();
182 if (table) {
183 const QTextTableCell cell = table->cellAt(textEdit->textCursor());
184 table->insertColumns(cell.column(), 1);
185 }
186 }
187}
188
189void TableActionMenuPrivate::_k_slotInsertColumnAfter()
190{
191 if (richTextMode) {
192 QTextTable *table = textEdit->textCursor().currentTable();
193 if (table) {
194 const QTextTableCell cell = table->cellAt(textEdit->textCursor());
195 if (cell.column() < table->columns()) {
196 table->insertColumns(cell.column() + 1, 1);
197 } else {
198 table->appendColumns(1);
199 }
200 }
201 }
202}
203
204void TableActionMenuPrivate::_k_slotInsertTable()
205{
206 if (richTextMode) {
207 QPointer<InsertTableDialog> dialog = new InsertTableDialog(textEdit);
208 if (dialog->exec()) {
209 QTextCursor cursor = textEdit->textCursor();
210 QTextTableFormat tableFormat;
211 tableFormat.setBorder(dialog->border());
212 const int numberOfColumns(dialog->columns());
213 QList<QTextLength> constrains;
214 constrains.reserve(numberOfColumns);
215 const QTextLength::Type type = dialog->typeOfLength();
216 const int length = dialog->length();
217
218 const QTextLength textlength(type, length / numberOfColumns);
219 for (int i = 0; i < numberOfColumns; ++i) {
220 constrains.append(textlength);
221 }
222 tableFormat.setColumnWidthConstraints(constrains);
223 tableFormat.setAlignment(Qt::AlignLeft);
224 QTextTable *table = cursor.insertTable(dialog->rows(), numberOfColumns);
225 table->setFormat(tableFormat);
226 }
227 delete dialog;
228 }
229}
230
231void TableActionMenuPrivate::_k_slotMergeCell()
232{
233 if (richTextMode) {
234 QTextTable *table = textEdit->textCursor().currentTable();
235 if (table) {
236 const QTextTableCell cell = table->cellAt(textEdit->textCursor());
237 table->mergeCells(cell.row(), cell.column(), 1, cell.columnSpan() + 1);
238 }
239 }
240}
241
242void TableActionMenuPrivate::_k_slotMergeSelectedCells()
243{
244 if (richTextMode) {
245 QTextTable *table = textEdit->textCursor().currentTable();
246 if (table) {
247 table->mergeCells(textEdit->textCursor());
248 }
249 }
250}
251
252void TableActionMenuPrivate::_k_slotTableFormat()
253{
254 if (richTextMode) {
255 QTextTable *table = textEdit->textCursor().currentTable();
256 if (table) {
257 QPointer<TableFormatDialog> dialog = new TableFormatDialog(textEdit);
258 const int numberOfColumn(table->columns());
259 const int numberOfRow(table->rows());
260 dialog->setColumns(numberOfColumn);
261 dialog->setRows(numberOfRow);
262 QTextTableFormat tableFormat = table->format();
263 dialog->setBorder(tableFormat.border());
264 dialog->setSpacing(tableFormat.cellSpacing());
265 dialog->setPadding(tableFormat.cellPadding());
266 dialog->setAlignment(tableFormat.alignment());
267 if (tableFormat.hasProperty(QTextFormat::BackgroundBrush)) {
268 dialog->setTableBackgroundColor(tableFormat.background().color());
269 }
270 QList<QTextLength> constrains = tableFormat.columnWidthConstraints();
271 if (!constrains.isEmpty()) {
272 dialog->setTypeOfLength(constrains.at(0).type());
273 dialog->setLength(constrains.at(0).rawValue() * numberOfColumn);
274 }
275
276 if (dialog->exec()) {
277 const int newNumberOfColumns(dialog->columns());
278 if ((newNumberOfColumns != numberOfColumn) || (dialog->rows() != numberOfRow)) {
279 table->resize(dialog->rows(), newNumberOfColumns);
280 }
281 tableFormat.setBorder(dialog->border());
282 tableFormat.setCellPadding(dialog->padding());
283 tableFormat.setCellSpacing(dialog->spacing());
284 tableFormat.setAlignment(dialog->alignment());
285
286 QList<QTextLength> constrainsText;
287 constrainsText.reserve(newNumberOfColumns);
288 const QTextLength::Type type = dialog->typeOfLength();
289 const int length = dialog->length();
290
291 const QTextLength textlength(type, length / newNumberOfColumns);
292 for (int i = 0; i < newNumberOfColumns; ++i) {
293 constrainsText.append(textlength);
294 }
295 tableFormat.setColumnWidthConstraints(constrainsText);
296 const QColor tableBackgroundColor = dialog->tableBackgroundColor();
297 if (dialog->useBackgroundColor()) {
298 if (tableBackgroundColor.isValid()) {
299 tableFormat.setBackground(tableBackgroundColor);
300 }
301 } else {
302 tableFormat.clearBackground();
303 }
304 table->setFormat(tableFormat);
305 }
306 delete dialog;
307 }
308 }
309}
310
311void TableActionMenuPrivate::_k_slotTableCellFormat()
312{
313 if (richTextMode) {
314 QTextTable *table = textEdit->textCursor().currentTable();
315 if (table) {
316 QTextTableCell cell = table->cellAt(textEdit->textCursor());
317 QPointer<TableCellFormatDialog> dialog = new TableCellFormatDialog(textEdit);
320 dialog->setTableCellBackgroundColor(format.background().color());
321 }
322 dialog->setVerticalAlignment(format.verticalAlignment());
323 if (dialog->exec()) {
324 if (dialog->useBackgroundColor()) {
325 const QColor tableCellColor = dialog->tableCellBackgroundColor();
326 if (tableCellColor.isValid()) {
327 format.setBackground(tableCellColor);
328 }
329 } else {
330 format.clearBackground();
331 }
332 format.setVerticalAlignment(dialog->verticalAlignment());
333 cell.setFormat(format);
334 }
335 delete dialog;
336 }
337 }
338}
339
340void TableActionMenuPrivate::_k_slotSplitCell()
341{
342 if (richTextMode) {
343 QTextTable *table = textEdit->textCursor().currentTable();
344 if (table) {
345 const QTextTableCell cell = table->cellAt(textEdit->textCursor());
346 if (cell.columnSpan() > 1 || cell.rowSpan() > 1) {
347 table->splitCell(cell.row(), cell.column(), qMax(1, cell.rowSpan() - 1), qMax(1, cell.columnSpan() - 1));
348 _k_updateActions();
349 }
350 }
351 }
352}
353
354void TableActionMenuPrivate::_k_updateActions(bool forceUpdate)
355{
356 if ((richTextMode) || forceUpdate) {
357 QTextTable *table = textEdit->textCursor().currentTable();
358 const bool isTable = (table != nullptr);
359 actionInsertRowBelow->setEnabled(isTable);
360 actionInsertRowAbove->setEnabled(isTable);
361
362 actionInsertColumnBefore->setEnabled(isTable);
363 actionInsertColumnAfter->setEnabled(isTable);
364
365 actionRemoveRowBelow->setEnabled(isTable);
366 actionRemoveRowAbove->setEnabled(isTable);
367
368 actionRemoveColumnBefore->setEnabled(isTable);
369 actionRemoveColumnAfter->setEnabled(isTable);
370
371 if (table) {
372 const QTextTableCell cell = table->cellAt(textEdit->textCursor());
373
374 int firstRow = -1;
375 int numRows = -1;
376 int firstColumn = -1;
377 int numColumns = -1;
378 textEdit->textCursor().selectedTableCells(&firstRow, &numRows, &firstColumn, &numColumns);
379 const bool hasSelectedTableCell = (firstRow != -1) && (numRows != -1) && (firstColumn != -1) && (numColumns != -1);
380 if (cell.column() > table->columns() - 2) {
381 actionMergeCell->setEnabled(false);
382 } else {
383 actionMergeCell->setEnabled(true);
384 }
385 if (cell.columnSpan() > 1 || cell.rowSpan() > 1) {
386 actionSplitCell->setEnabled(true);
387 } else {
388 actionSplitCell->setEnabled(false);
389 }
390 actionTableCellFormat->setEnabled(true);
391 actionMergeSelectedCells->setEnabled(hasSelectedTableCell);
392 } else {
393 actionSplitCell->setEnabled(false);
394 actionMergeCell->setEnabled(false);
395 actionMergeSelectedCells->setEnabled(false);
396 }
397 actionTableFormat->setEnabled(isTable);
398 actionTableCellFormat->setEnabled(isTable);
399 actionRemoveCellContents->setEnabled(isTable);
400 }
401}
402
403TableActionMenu::TableActionMenu(QTextEdit *textEdit)
404 : KActionMenu(textEdit)
405 , d(new TableActionMenuPrivate(textEdit, this))
406{
407 auto insertMenu = new KActionMenu(i18n("Insert"), this);
408 addAction(insertMenu);
409
410 d->actionInsertTable = new QAction(QIcon::fromTheme(QStringLiteral("insert-table")), i18n("Table…"), this);
411 d->actionInsertTable->setObjectName("insert_new_table"_L1);
412 insertMenu->addAction(d->actionInsertTable);
413 connect(d->actionInsertTable, &QAction::triggered, this, [this]() {
414 d->_k_slotInsertTable();
415 });
416
417 insertMenu->addSeparator();
418 d->actionInsertRowBelow = new QAction(QIcon::fromTheme(QStringLiteral("edit-table-insert-row-below")), i18n("Row Below"), this);
419 insertMenu->addAction(d->actionInsertRowBelow);
420 d->actionInsertRowBelow->setObjectName("insert_row_below"_L1);
421 connect(d->actionInsertRowBelow, &QAction::triggered, this, [this]() {
422 d->_k_slotInsertRowBelow();
423 });
424
425 d->actionInsertRowAbove = new QAction(QIcon::fromTheme(QStringLiteral("edit-table-insert-row-above")), i18n("Row Above"), this);
426 insertMenu->addAction(d->actionInsertRowAbove);
427 d->actionInsertRowAbove->setObjectName("insert_row_above"_L1);
428 connect(d->actionInsertRowAbove, &QAction::triggered, this, [this]() {
429 d->_k_slotInsertRowAbove();
430 });
431
432 insertMenu->addSeparator();
433 d->actionInsertColumnBefore = new QAction(QIcon::fromTheme(QStringLiteral("edit-table-insert-column-left")), i18n("Column Before"), this);
434 insertMenu->addAction(d->actionInsertColumnBefore);
435 d->actionInsertColumnBefore->setObjectName("insert_column_before"_L1);
436
437 connect(d->actionInsertColumnBefore, &QAction::triggered, this, [this]() {
438 d->_k_slotInsertColumnBefore();
439 });
440
441 d->actionInsertColumnAfter = new QAction(QIcon::fromTheme(QStringLiteral("edit-table-insert-column-right")), i18n("Column After"), this);
442 insertMenu->addAction(d->actionInsertColumnAfter);
443 d->actionInsertColumnAfter->setObjectName("insert_column_after"_L1);
444 connect(d->actionInsertColumnAfter, &QAction::triggered, this, [this]() {
445 d->_k_slotInsertColumnAfter();
446 });
447
448 auto removeMenu = new KActionMenu(i18n("Delete"), this);
449 addAction(removeMenu);
450
451 d->actionRemoveRowBelow = new QAction(i18nc("@action", "Row Below"), this);
452 removeMenu->addAction(d->actionRemoveRowBelow);
453 d->actionRemoveRowBelow->setObjectName("remove_row_below"_L1);
454 connect(d->actionRemoveRowBelow, &QAction::triggered, this, [this]() {
455 d->_k_slotRemoveRowBelow();
456 });
457
458 d->actionRemoveRowAbove = new QAction(i18nc("@action", "Row Above"), this);
459 removeMenu->addAction(d->actionRemoveRowAbove);
460 d->actionRemoveRowAbove->setObjectName("remove_row_above"_L1);
461 connect(d->actionRemoveRowAbove, &QAction::triggered, this, [this]() {
462 d->_k_slotRemoveRowAbove();
463 });
464
465 removeMenu->addSeparator();
466 d->actionRemoveColumnBefore = new QAction(i18nc("@action", "Column Before"), this);
467 removeMenu->addAction(d->actionRemoveColumnBefore);
468 d->actionRemoveColumnBefore->setObjectName("remove_column_before"_L1);
469
470 connect(d->actionRemoveColumnBefore, &QAction::triggered, this, [this]() {
471 d->_k_slotRemoveColumnBefore();
472 });
473
474 d->actionRemoveColumnAfter = new QAction(i18nc("@action", "Column After"), this);
475 removeMenu->addAction(d->actionRemoveColumnAfter);
476 d->actionRemoveColumnAfter->setObjectName("remove_column_after"_L1);
477 connect(d->actionRemoveColumnAfter, &QAction::triggered, this, [this]() {
478 d->_k_slotRemoveColumnAfter();
479 });
480
481 removeMenu->addSeparator();
482 d->actionRemoveCellContents = new QAction(i18nc("@action", "Cell Contents"), this);
483 removeMenu->addAction(d->actionRemoveCellContents);
484 d->actionRemoveCellContents->setObjectName("remove_cell_contents"_L1);
485 connect(d->actionRemoveCellContents, &QAction::triggered, this, [this]() {
486 d->_k_slotRemoveCellContents();
487 });
488
489 addSeparator();
490
491 d->actionMergeCell = new QAction(QIcon::fromTheme(QStringLiteral("edit-table-cell-merge")), i18n("Join With Cell to the Right"), this);
492 d->actionMergeCell->setObjectName("join_cell_to_the_right"_L1);
493 connect(d->actionMergeCell, &QAction::triggered, this, [this]() {
494 d->_k_slotMergeCell();
495 });
496 addAction(d->actionMergeCell);
497
498 d->actionMergeSelectedCells = new QAction(i18nc("@action", "Join Selected Cells"), this);
499 d->actionMergeSelectedCells->setObjectName("join_cell_selected_cells"_L1);
500 connect(d->actionMergeSelectedCells, &QAction::triggered, this, [this]() {
501 d->_k_slotMergeSelectedCells();
502 });
503 addAction(d->actionMergeSelectedCells);
504
505 d->actionSplitCell = new QAction(QIcon::fromTheme(QStringLiteral("edit-table-cell-split")), i18n("Split cells"), this);
506 d->actionSplitCell->setObjectName("split_cells"_L1);
507 connect(d->actionSplitCell, &QAction::triggered, this, [this]() {
508 d->_k_slotSplitCell();
509 });
510 addAction(d->actionSplitCell);
511
512 addSeparator();
513
514 d->actionTableFormat = new QAction(i18nc("@action", "Table Format…"), this);
515 d->actionTableFormat->setObjectName("table_format"_L1);
516 connect(d->actionTableFormat, &QAction::triggered, this, [this]() {
517 d->_k_slotTableFormat();
518 });
519 addAction(d->actionTableFormat);
520
521 d->actionTableCellFormat = new QAction(i18nc("@action", "Table Cell Format…"), this);
522 d->actionTableCellFormat->setObjectName("table_cell_format"_L1);
523 connect(d->actionTableCellFormat, &QAction::triggered, this, [this]() {
524 d->_k_slotTableCellFormat();
525 });
526 addAction(d->actionTableCellFormat);
527
528 connect(textEdit, &QTextEdit::cursorPositionChanged, this, [this]() {
529 d->_k_updateActions(false);
530 });
531 d->_k_updateActions(true);
532}
533
534TableActionMenu::~TableActionMenu() = default;
535
536void TableActionMenu::setRichTextMode(bool richTextMode)
537{
538 d->richTextMode = richTextMode;
539}
540}
541
542#include "moc_tableactionmenu.cpp"
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
Type type(const QSqlDatabase &db)
void setEnabled(bool)
void triggered(bool checked)
const QColor & color() const const
bool isValid() const const
QIcon fromTheme(const QString &name)
void append(QList< T > &&value)
const_reference at(qsizetype i) const const
bool isEmpty() const const
void reserve(qsizetype size)
AlignLeft
void setVerticalAlignment(VerticalAlignment alignment)
VerticalAlignment verticalAlignment() const const
void beginEditBlock()
QTextTable * currentTable() const const
void endEditBlock()
QTextTable * insertTable(int rows, int columns)
bool movePosition(MoveOperation operation, MoveMode mode, int n)
int position() const const
void removeSelectedText()
void selectedTableCells(int *firstRow, int *numRows, int *firstColumn, int *numColumns) const const
void setPosition(int pos, MoveMode m)
void cursorPositionChanged()
QTextCursor textCursor() const const
QBrush background() const const
void clearBackground()
bool hasProperty(int propertyId) const const
void setBackground(const QBrush &brush)
QTextTableCellFormat toTableCellFormat() const const
qreal border() const const
void setBorder(qreal width)
void appendColumns(int count)
void appendRows(int count)
QTextTableCell cellAt(const QTextCursor &cursor) const const
int columns() const const
QTextTableFormat format() const const
void insertColumns(int index, int columns)
void insertRows(int index, int rows)
void mergeCells(const QTextCursor &cursor)
void removeColumns(int index, int columns)
void removeRows(int index, int rows)
void resize(int rows, int columns)
int rows() const const
void setFormat(const QTextTableFormat &format)
void splitCell(int row, int column, int numRows, int numCols)
int column() const const
int columnSpan() const const
QTextCursor firstCursorPosition() const const
QTextCharFormat format() const const
bool isValid() const const
QTextCursor lastCursorPosition() const const
int row() const const
int rowSpan() const const
void setFormat(const QTextCharFormat &format)
Qt::Alignment alignment() const const
qreal cellPadding() const const
qreal cellSpacing() const const
QList< QTextLength > columnWidthConstraints() const const
void setAlignment(Qt::Alignment alignment)
void setCellPadding(qreal padding)
void setCellSpacing(qreal spacing)
void setColumnWidthConstraints(const QList< QTextLength > &constraints)
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Sat Dec 21 2024 16:55:51 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.