1 | /* |
2 | This file is part of the KDE libraries |
3 | SPDX-FileCopyrightText: 2008 Stephen Kelly <steveire@gmail.com> |
4 | SPDX-FileCopyrightText: 2008 Thomas McGuire <thomas.mcguire@gmx.net> |
5 | |
6 | SPDX-License-Identifier: LGPL-2.0-only |
7 | */ |
8 | |
9 | #include "krichtextwidget.h" |
10 | |
11 | #include "krichtextedit_p.h" |
12 | |
13 | // KDE includes |
14 | #include <KColorScheme> |
15 | #include <KFontAction> |
16 | #include <KFontSizeAction> |
17 | #include <KLocalizedString> |
18 | #include <ktoggleaction.h> |
19 | |
20 | // Qt includes |
21 | #include <QAction> |
22 | #include <QActionGroup> |
23 | #include <QColorDialog> |
24 | #include <QTextList> |
25 | |
26 | #include "klinkdialog_p.h" |
27 | |
28 | // TODO: Add i18n context |
29 | |
30 | /** |
31 | Private class that helps to provide binary compatibility between releases. |
32 | @internal |
33 | */ |
34 | //@cond PRIVATE |
35 | class KRichTextWidgetPrivate : public KRichTextEditPrivate |
36 | { |
37 | Q_DECLARE_PUBLIC(KRichTextWidget) |
38 | |
39 | public: |
40 | KRichTextWidgetPrivate(KRichTextWidget *qq) |
41 | : KRichTextEditPrivate(qq) |
42 | { |
43 | } |
44 | |
45 | QList<QAction *> richTextActionList; |
46 | QTextCharFormat painterFormat; |
47 | |
48 | KRichTextWidget::RichTextSupport richTextSupport; |
49 | |
50 | bool painterActive = false; |
51 | |
52 | bool richTextEnabled = false; |
53 | KToggleAction *enableRichText = nullptr; |
54 | |
55 | QAction *action_text_foreground_color = nullptr; |
56 | QAction *action_text_background_color = nullptr; |
57 | |
58 | KToggleAction *action_text_bold = nullptr; |
59 | KToggleAction *action_text_italic = nullptr; |
60 | KToggleAction *action_text_underline = nullptr; |
61 | KToggleAction *action_text_strikeout = nullptr; |
62 | |
63 | KFontAction *action_font_family = nullptr; |
64 | KFontSizeAction *action_font_size = nullptr; |
65 | |
66 | KSelectAction *action_list_style = nullptr; |
67 | QAction *action_list_indent = nullptr; |
68 | QAction *action_list_dedent = nullptr; |
69 | |
70 | QAction *action_manage_link = nullptr; |
71 | QAction *action_insert_horizontal_rule = nullptr; |
72 | QAction *action_format_painter = nullptr; |
73 | QAction *action_to_plain_text = nullptr; |
74 | |
75 | KToggleAction *action_align_left = nullptr; |
76 | KToggleAction *action_align_right = nullptr; |
77 | KToggleAction *action_align_center = nullptr; |
78 | KToggleAction *action_align_justify = nullptr; |
79 | |
80 | KToggleAction *action_direction_ltr = nullptr; |
81 | KToggleAction *action_direction_rtl = nullptr; |
82 | |
83 | KToggleAction *action_text_superscript = nullptr; |
84 | KToggleAction *action_text_subscript = nullptr; |
85 | |
86 | KSelectAction *action_heading_level = nullptr; |
87 | |
88 | // |
89 | // Normal functions |
90 | // |
91 | void init(); |
92 | |
93 | // |
94 | // Slots |
95 | // |
96 | |
97 | /** |
98 | * @brief Opens a dialog to allow the user to select a foreground color. |
99 | */ |
100 | void _k_setTextForegroundColor(); |
101 | |
102 | /** |
103 | * @brief Opens a dialog to allow the user to select a background color. |
104 | */ |
105 | void _k_setTextBackgroundColor(); |
106 | |
107 | /** |
108 | * Opens a dialog which lets the user turn the currently selected text into |
109 | * a link. |
110 | * If no text is selected, the word under the cursor will be taken. |
111 | * If the cursor is already over a link, the user can edit that link. |
112 | * |
113 | */ |
114 | void _k_manageLink(); |
115 | |
116 | /** |
117 | * Activates a format painter to allow the user to copy font/text formatting |
118 | * to different parts of the document. |
119 | * |
120 | */ |
121 | void _k_formatPainter(bool active); |
122 | |
123 | /** |
124 | * @brief Update actions relating to text format (bold, size etc.). |
125 | */ |
126 | void updateCharFormatActions(const QTextCharFormat &format); |
127 | |
128 | /** |
129 | * Update actions not covered by text formatting, such as alignment, |
130 | * list style and level. |
131 | */ |
132 | void updateMiscActions(); |
133 | |
134 | /** |
135 | * Change the style of the current list or create a new list with the style given by @a index. |
136 | */ |
137 | void _k_setListStyle(int index); |
138 | |
139 | /** |
140 | * Change the heading level of a current line to a level given by @a level |
141 | */ |
142 | void _k_setHeadingLevel(int level); |
143 | }; |
144 | //@endcond |
145 | |
146 | void KRichTextWidgetPrivate::init() |
147 | { |
148 | Q_Q(KRichTextWidget); |
149 | |
150 | q->setRichTextSupport(KRichTextWidget::FullSupport); |
151 | } |
152 | |
153 | KRichTextWidget::KRichTextWidget(QWidget *parent) |
154 | : KRichTextEdit(*new KRichTextWidgetPrivate(this), parent) |
155 | { |
156 | Q_D(KRichTextWidget); |
157 | |
158 | d->init(); |
159 | } |
160 | |
161 | KRichTextWidget::KRichTextWidget(const QString &text, QWidget *parent) |
162 | : KRichTextEdit(*new KRichTextWidgetPrivate(this), text, parent) |
163 | { |
164 | Q_D(KRichTextWidget); |
165 | |
166 | d->init(); |
167 | } |
168 | |
169 | KRichTextWidget::~KRichTextWidget() = default; |
170 | |
171 | KRichTextWidget::RichTextSupport KRichTextWidget::richTextSupport() const |
172 | { |
173 | Q_D(const KRichTextWidget); |
174 | |
175 | return d->richTextSupport; |
176 | } |
177 | |
178 | void KRichTextWidget::setRichTextSupport(const KRichTextWidget::RichTextSupport &support) |
179 | { |
180 | Q_D(KRichTextWidget); |
181 | |
182 | d->richTextSupport = support; |
183 | } |
184 | |
185 | QList<QAction *> KRichTextWidget::createActions() |
186 | { |
187 | Q_D(KRichTextWidget); |
188 | |
189 | // Note to maintainers: If adding new functionality here, make sure to disconnect |
190 | // and delete actions which should not be supported. |
191 | // |
192 | // New Actions need to be added to the following places: |
193 | // - possibly the RichTextSupportValues enum |
194 | // - the API documentation for createActions() |
195 | // - this function |
196 | // - the action needs to be added to the private class as a member |
197 | // - the constructor of the private class |
198 | // - depending on the action, some slot that changes the toggle state when |
199 | // appropriate, such as updateCharFormatActions or updateMiscActions. |
200 | |
201 | // The list of actions currently supported is also stored internally. |
202 | // This is used to disable all actions at once in setActionsEnabled. |
203 | d->richTextActionList.clear(); |
204 | |
205 | if (d->richTextSupport & SupportTextForegroundColor) { |
206 | // Foreground Color |
207 | d->action_text_foreground_color = new QAction(QIcon::fromTheme(QStringLiteral("format-stroke-color" )), i18nc("@action" , "Text &Color..." ), this); |
208 | d->action_text_foreground_color->setIconText(i18nc("@label stroke color" , "Color" )); |
209 | d->richTextActionList.append(t: (d->action_text_foreground_color)); |
210 | d->action_text_foreground_color->setObjectName(QStringLiteral("format_text_foreground_color" )); |
211 | connect(sender: d->action_text_foreground_color, signal: &QAction::triggered, context: this, slot: [this]() { |
212 | Q_D(KRichTextWidget); |
213 | d->_k_setTextForegroundColor(); |
214 | }); |
215 | } else { |
216 | d->action_text_foreground_color = nullptr; |
217 | } |
218 | |
219 | if (d->richTextSupport & SupportTextBackgroundColor) { |
220 | // Background Color |
221 | d->action_text_background_color = new QAction(QIcon::fromTheme(QStringLiteral("format-fill-color" )), i18nc("@action" , "Text &Highlight..." ), this); |
222 | d->richTextActionList.append(t: (d->action_text_background_color)); |
223 | d->action_text_background_color->setObjectName(QStringLiteral("format_text_background_color" )); |
224 | connect(sender: d->action_text_background_color, signal: &QAction::triggered, context: this, slot: [this]() { |
225 | Q_D(KRichTextWidget); |
226 | d->_k_setTextBackgroundColor(); |
227 | }); |
228 | } else { |
229 | d->action_text_background_color = nullptr; |
230 | } |
231 | |
232 | if (d->richTextSupport & SupportFontFamily) { |
233 | // Font Family |
234 | d->action_font_family = new KFontAction(i18nc("@action" , "&Font" ), this); |
235 | d->richTextActionList.append(t: (d->action_font_family)); |
236 | d->action_font_family->setObjectName(QStringLiteral("format_font_family" )); |
237 | connect(sender: d->action_font_family, signal: &KSelectAction::textTriggered, context: this, slot: &KRichTextWidget::setFontFamily); |
238 | } else { |
239 | d->action_font_family = nullptr; |
240 | } |
241 | |
242 | if (d->richTextSupport & SupportFontSize) { |
243 | // Font Size |
244 | d->action_font_size = new KFontSizeAction(i18nc("@action" , "Font &Size" ), this); |
245 | d->richTextActionList.append(t: (d->action_font_size)); |
246 | d->action_font_size->setObjectName(QStringLiteral("format_font_size" )); |
247 | connect(sender: d->action_font_size, signal: &KFontSizeAction::fontSizeChanged, context: this, slot: &KRichTextEdit::setFontSize); |
248 | } else { |
249 | d->action_font_size = nullptr; |
250 | } |
251 | |
252 | if (d->richTextSupport & SupportBold) { |
253 | d->action_text_bold = new KToggleAction(QIcon::fromTheme(QStringLiteral("format-text-bold" )), i18nc("@action boldify selected text" , "&Bold" ), this); |
254 | QFont bold; |
255 | bold.setBold(true); |
256 | d->action_text_bold->setFont(bold); |
257 | d->richTextActionList.append(t: (d->action_text_bold)); |
258 | d->action_text_bold->setObjectName(QStringLiteral("format_text_bold" )); |
259 | d->action_text_bold->setShortcut(Qt::CTRL | Qt::Key_B); |
260 | connect(sender: d->action_text_bold, signal: &QAction::triggered, context: this, slot: &KRichTextEdit::setTextBold); |
261 | } else { |
262 | d->action_text_bold = nullptr; |
263 | } |
264 | |
265 | if (d->richTextSupport & SupportItalic) { |
266 | d->action_text_italic = |
267 | new KToggleAction(QIcon::fromTheme(QStringLiteral("format-text-italic" )), i18nc("@action italicize selected text" , "&Italic" ), this); |
268 | QFont italic; |
269 | italic.setItalic(true); |
270 | d->action_text_italic->setFont(italic); |
271 | d->richTextActionList.append(t: (d->action_text_italic)); |
272 | d->action_text_italic->setObjectName(QStringLiteral("format_text_italic" )); |
273 | d->action_text_italic->setShortcut(Qt::CTRL | Qt::Key_I); |
274 | connect(sender: d->action_text_italic, signal: &QAction::triggered, context: this, slot: &KRichTextEdit::setTextItalic); |
275 | } else { |
276 | d->action_text_italic = nullptr; |
277 | } |
278 | |
279 | if (d->richTextSupport & SupportUnderline) { |
280 | d->action_text_underline = |
281 | new KToggleAction(QIcon::fromTheme(QStringLiteral("format-text-underline" )), i18nc("@action underline selected text" , "&Underline" ), this); |
282 | QFont underline; |
283 | underline.setUnderline(true); |
284 | d->action_text_underline->setFont(underline); |
285 | d->richTextActionList.append(t: (d->action_text_underline)); |
286 | d->action_text_underline->setObjectName(QStringLiteral("format_text_underline" )); |
287 | d->action_text_underline->setShortcut(Qt::CTRL | Qt::Key_U); |
288 | connect(sender: d->action_text_underline, signal: &QAction::triggered, context: this, slot: &KRichTextEdit::setTextUnderline); |
289 | } else { |
290 | d->action_text_underline = nullptr; |
291 | } |
292 | |
293 | if (d->richTextSupport & SupportStrikeOut) { |
294 | d->action_text_strikeout = new KToggleAction(QIcon::fromTheme(QStringLiteral("format-text-strikethrough" )), i18nc("@action" , "&Strike Out" ), this); |
295 | QFont strikeout; |
296 | strikeout.setStrikeOut(true); |
297 | d->action_text_strikeout->setFont(strikeout); |
298 | d->richTextActionList.append(t: (d->action_text_strikeout)); |
299 | d->action_text_strikeout->setObjectName(QStringLiteral("format_text_strikeout" )); |
300 | d->action_text_strikeout->setShortcut(Qt::CTRL | Qt::Key_L); |
301 | connect(sender: d->action_text_strikeout, signal: &QAction::triggered, context: this, slot: &KRichTextEdit::setTextStrikeOut); |
302 | } else { |
303 | d->action_text_strikeout = nullptr; |
304 | } |
305 | |
306 | if (d->richTextSupport & SupportAlignment) { |
307 | // Alignment |
308 | d->action_align_left = new KToggleAction(QIcon::fromTheme(QStringLiteral("format-justify-left" )), i18nc("@action" , "Align &Left" ), this); |
309 | d->action_align_left->setIconText(i18nc("@label left justify" , "Left" )); |
310 | d->richTextActionList.append(t: (d->action_align_left)); |
311 | d->action_align_left->setObjectName(QStringLiteral("format_align_left" )); |
312 | connect(sender: d->action_align_left, signal: &QAction::triggered, context: this, slot: &KRichTextEdit::alignLeft); |
313 | |
314 | d->action_align_center = new KToggleAction(QIcon::fromTheme(QStringLiteral("format-justify-center" )), i18nc("@action" , "Align &Center" ), this); |
315 | d->action_align_center->setIconText(i18nc("@label center justify" , "Center" )); |
316 | d->richTextActionList.append(t: (d->action_align_center)); |
317 | d->action_align_center->setObjectName(QStringLiteral("format_align_center" )); |
318 | connect(sender: d->action_align_center, signal: &QAction::triggered, context: this, slot: &KRichTextEdit::alignCenter); |
319 | |
320 | d->action_align_right = new KToggleAction(QIcon::fromTheme(QStringLiteral("format-justify-right" )), i18nc("@action" , "Align &Right" ), this); |
321 | d->action_align_right->setIconText(i18nc("@label right justify" , "Right" )); |
322 | d->richTextActionList.append(t: (d->action_align_right)); |
323 | d->action_align_right->setObjectName(QStringLiteral("format_align_right" )); |
324 | connect(sender: d->action_align_right, signal: &QAction::triggered, context: this, slot: &KRichTextEdit::alignRight); |
325 | |
326 | d->action_align_justify = new KToggleAction(QIcon::fromTheme(QStringLiteral("format-justify-fill" )), i18nc("@action" , "&Justify" ), this); |
327 | d->action_align_justify->setIconText(i18nc("@label justify fill" , "Justify" )); |
328 | d->richTextActionList.append(t: (d->action_align_justify)); |
329 | d->action_align_justify->setObjectName(QStringLiteral("format_align_justify" )); |
330 | connect(sender: d->action_align_justify, signal: &QAction::triggered, context: this, slot: &KRichTextEdit::alignJustify); |
331 | |
332 | QActionGroup *alignmentGroup = new QActionGroup(this); |
333 | alignmentGroup->addAction(a: d->action_align_left); |
334 | alignmentGroup->addAction(a: d->action_align_center); |
335 | alignmentGroup->addAction(a: d->action_align_right); |
336 | alignmentGroup->addAction(a: d->action_align_justify); |
337 | } else { |
338 | d->action_align_left = nullptr; |
339 | d->action_align_center = nullptr; |
340 | d->action_align_right = nullptr; |
341 | d->action_align_justify = nullptr; |
342 | } |
343 | |
344 | if (d->richTextSupport & SupportDirection) { |
345 | d->action_direction_ltr = new KToggleAction(QIcon::fromTheme(QStringLiteral("format-text-direction-ltr" )), i18nc("@action" , "Left-to-Right" ), this); |
346 | d->action_direction_ltr->setIconText(i18nc("@label left-to-right" , "Left-to-Right" )); |
347 | d->richTextActionList.append(t: d->action_direction_ltr); |
348 | d->action_direction_ltr->setObjectName(QStringLiteral("direction_ltr" )); |
349 | connect(sender: d->action_direction_ltr, signal: &QAction::triggered, context: this, slot: &KRichTextEdit::makeLeftToRight); |
350 | |
351 | d->action_direction_rtl = new KToggleAction(QIcon::fromTheme(QStringLiteral("format-text-direction-rtl" )), i18nc("@action" , "Right-to-Left" ), this); |
352 | d->action_direction_rtl->setIconText(i18nc("@label right-to-left" , "Right-to-Left" )); |
353 | d->richTextActionList.append(t: d->action_direction_rtl); |
354 | d->action_direction_rtl->setObjectName(QStringLiteral("direction_rtl" )); |
355 | connect(sender: d->action_direction_rtl, signal: &QAction::triggered, context: this, slot: &KRichTextEdit::makeRightToLeft); |
356 | |
357 | QActionGroup *directionGroup = new QActionGroup(this); |
358 | directionGroup->addAction(a: d->action_direction_ltr); |
359 | directionGroup->addAction(a: d->action_direction_rtl); |
360 | } else { |
361 | d->action_direction_ltr = nullptr; |
362 | d->action_direction_rtl = nullptr; |
363 | } |
364 | |
365 | if (d->richTextSupport & SupportChangeListStyle) { |
366 | d->action_list_style = new KSelectAction(QIcon::fromTheme(QStringLiteral("format-list-unordered" )), i18nc("@title:menu" , "List Style" ), this); |
367 | QStringList listStyles; |
368 | /* clang-format off */ |
369 | listStyles << i18nc("@item:inmenu no list style" , "None" ) |
370 | << i18nc("@item:inmenu disc list style" , "Disc" ) |
371 | << i18nc("@item:inmenu circle list style" , "Circle" ) |
372 | << i18nc("@item:inmenu square list style" , "Square" ) |
373 | << i18nc("@item:inmenu numbered lists" , "123" ) |
374 | << i18nc("@item:inmenu lowercase abc lists" , "abc" ) |
375 | << i18nc("@item:inmenu uppercase abc lists" , "ABC" ) |
376 | << i18nc("@item:inmenu lower case roman numerals" , "i ii iii" ) |
377 | << i18nc("@item:inmenu upper case roman numerals" , "I II III" ); |
378 | /* clang-format on */ |
379 | |
380 | d->action_list_style->setItems(listStyles); |
381 | d->action_list_style->setCurrentItem(0); |
382 | d->richTextActionList.append(t: (d->action_list_style)); |
383 | d->action_list_style->setObjectName(QStringLiteral("format_list_style" )); |
384 | |
385 | connect(sender: d->action_list_style, signal: &KSelectAction::indexTriggered, context: this, slot: [this](int style) { |
386 | Q_D(KRichTextWidget); |
387 | d->_k_setListStyle(index: style); |
388 | }); |
389 | connect(sender: d->action_list_style, signal: &QAction::triggered, context: this, slot: [d]() { |
390 | d->updateMiscActions(); |
391 | }); |
392 | } else { |
393 | d->action_list_style = nullptr; |
394 | } |
395 | |
396 | if (d->richTextSupport & SupportIndentLists) { |
397 | d->action_list_indent = new QAction(QIcon::fromTheme(QStringLiteral("format-indent-more" )), i18nc("@action" , "Increase Indent" ), this); |
398 | d->richTextActionList.append(t: (d->action_list_indent)); |
399 | d->action_list_indent->setObjectName(QStringLiteral("format_list_indent_more" )); |
400 | connect(sender: d->action_list_indent, signal: &QAction::triggered, context: this, slot: &KRichTextEdit::indentListMore); |
401 | connect(sender: d->action_list_indent, signal: &QAction::triggered, context: this, slot: [d]() { |
402 | d->updateMiscActions(); |
403 | }); |
404 | } else { |
405 | d->action_list_indent = nullptr; |
406 | } |
407 | |
408 | if (d->richTextSupport & SupportDedentLists) { |
409 | d->action_list_dedent = new QAction(QIcon::fromTheme(QStringLiteral("format-indent-less" )), i18nc("@action" , "Decrease Indent" ), this); |
410 | d->richTextActionList.append(t: (d->action_list_dedent)); |
411 | d->action_list_dedent->setObjectName(QStringLiteral("format_list_indent_less" )); |
412 | connect(sender: d->action_list_dedent, signal: &QAction::triggered, context: this, slot: &KRichTextEdit::indentListLess); |
413 | connect(sender: d->action_list_dedent, signal: &QAction::triggered, context: this, slot: [d]() { |
414 | d->updateMiscActions(); |
415 | }); |
416 | } else { |
417 | d->action_list_dedent = nullptr; |
418 | } |
419 | |
420 | if (d->richTextSupport & SupportRuleLine) { |
421 | d->action_insert_horizontal_rule = new QAction(QIcon::fromTheme(QStringLiteral("insert-horizontal-rule" )), i18nc("@action" , "Insert Rule Line" ), this); |
422 | d->richTextActionList.append(t: (d->action_insert_horizontal_rule)); |
423 | d->action_insert_horizontal_rule->setObjectName(QStringLiteral("insert_horizontal_rule" )); |
424 | connect(sender: d->action_insert_horizontal_rule, signal: &QAction::triggered, context: this, slot: &KRichTextEdit::insertHorizontalRule); |
425 | } else { |
426 | d->action_insert_horizontal_rule = nullptr; |
427 | } |
428 | |
429 | if (d->richTextSupport & SupportHyperlinks) { |
430 | d->action_manage_link = new QAction(QIcon::fromTheme(QStringLiteral("insert-link" )), i18nc("@action" , "Link" ), this); |
431 | d->richTextActionList.append(t: (d->action_manage_link)); |
432 | d->action_manage_link->setObjectName(QStringLiteral("manage_link" )); |
433 | connect(sender: d->action_manage_link, signal: &QAction::triggered, context: this, slot: [this]() { |
434 | Q_D(KRichTextWidget); |
435 | d->_k_manageLink(); |
436 | }); |
437 | } else { |
438 | d->action_manage_link = nullptr; |
439 | } |
440 | |
441 | if (d->richTextSupport & SupportFormatPainting) { |
442 | d->action_format_painter = new KToggleAction(QIcon::fromTheme(QStringLiteral("draw-brush" )), i18nc("@action" , "Format Painter" ), this); |
443 | d->richTextActionList.append(t: (d->action_format_painter)); |
444 | d->action_format_painter->setObjectName(QStringLiteral("format_painter" )); |
445 | connect(sender: d->action_format_painter, signal: &QAction::toggled, context: this, slot: [this](bool state) { |
446 | Q_D(KRichTextWidget); |
447 | d->_k_formatPainter(active: state); |
448 | }); |
449 | } else { |
450 | d->action_format_painter = nullptr; |
451 | } |
452 | |
453 | if (d->richTextSupport & SupportToPlainText) { |
454 | d->action_to_plain_text = new KToggleAction(i18nc("@action" , "To Plain Text" ), this); |
455 | d->richTextActionList.append(t: (d->action_to_plain_text)); |
456 | d->action_to_plain_text->setObjectName(QStringLiteral("action_to_plain_text" )); |
457 | connect(sender: d->action_to_plain_text, signal: &QAction::triggered, context: this, slot: &KRichTextEdit::switchToPlainText); |
458 | } else { |
459 | d->action_to_plain_text = nullptr; |
460 | } |
461 | |
462 | if (d->richTextSupport & SupportSuperScriptAndSubScript) { |
463 | d->action_text_subscript = new KToggleAction(QIcon::fromTheme(QStringLiteral("format-text-subscript" )), i18nc("@action" , "Subscript" ), this); |
464 | d->richTextActionList.append(t: (d->action_text_subscript)); |
465 | d->action_text_subscript->setObjectName(QStringLiteral("format_text_subscript" )); |
466 | connect(sender: d->action_text_subscript, signal: &QAction::triggered, context: this, slot: &KRichTextEdit::setTextSubScript); |
467 | |
468 | d->action_text_superscript = new KToggleAction(QIcon::fromTheme(QStringLiteral("format-text-superscript" )), i18nc("@action" , "Superscript" ), this); |
469 | d->richTextActionList.append(t: (d->action_text_superscript)); |
470 | d->action_text_superscript->setObjectName(QStringLiteral("format_text_superscript" )); |
471 | connect(sender: d->action_text_superscript, signal: &QAction::triggered, context: this, slot: &KRichTextEdit::setTextSuperScript); |
472 | } else { |
473 | d->action_text_subscript = nullptr; |
474 | |
475 | d->action_text_superscript = nullptr; |
476 | } |
477 | |
478 | if (d->richTextSupport & SupportHeading) { |
479 | // TODO: an icon maybe? |
480 | d->action_heading_level = new KSelectAction(i18nc("@title:menu" , "Heading Level" ), this); |
481 | const QStringList headingLevels = {i18nc("@item:inmenu no heading" , "Basic text" ), |
482 | i18nc("@item:inmenu heading level 1 (largest)" , "Title" ), |
483 | i18nc("@item:inmenu heading level 2" , "Subtitle" ), |
484 | i18nc("@item:inmenu heading level 3" , "Section" ), |
485 | i18nc("@item:inmenu heading level 4" , "Subsection" ), |
486 | i18nc("@item:inmenu heading level 5" , "Paragraph" ), |
487 | i18nc("@item:inmenu heading level 6 (smallest)" , "Subparagraph" )}; |
488 | |
489 | d->action_heading_level->setItems(headingLevels); |
490 | d->action_heading_level->setCurrentItem(0); |
491 | d->richTextActionList.append(t: d->action_heading_level); |
492 | d->action_heading_level->setObjectName(QStringLiteral("format_heading_level" )); |
493 | connect(sender: d->action_heading_level, signal: &KSelectAction::indexTriggered, context: this, slot: [this](int level) { |
494 | Q_D(KRichTextWidget); |
495 | d->_k_setHeadingLevel(level); |
496 | }); |
497 | } else { |
498 | d->action_heading_level = nullptr; |
499 | } |
500 | |
501 | disconnect(sender: this, signal: &QTextEdit::currentCharFormatChanged, receiver: this, zero: nullptr); |
502 | disconnect(sender: this, signal: &QTextEdit::cursorPositionChanged, receiver: this, zero: nullptr); |
503 | connect(sender: this, signal: &QTextEdit::currentCharFormatChanged, context: this, slot: [d](const QTextCharFormat &format) { |
504 | d->updateCharFormatActions(format); |
505 | }); |
506 | connect(sender: this, signal: &QTextEdit::cursorPositionChanged, context: this, slot: [d]() { |
507 | d->updateMiscActions(); |
508 | }); |
509 | |
510 | d->updateMiscActions(); |
511 | d->updateCharFormatActions(format: currentCharFormat()); |
512 | |
513 | return d->richTextActionList; |
514 | } |
515 | |
516 | void KRichTextWidget::setActionsEnabled(bool enabled) |
517 | { |
518 | Q_D(KRichTextWidget); |
519 | |
520 | for (QAction *action : std::as_const(t&: d->richTextActionList)) { |
521 | action->setEnabled(enabled); |
522 | } |
523 | d->richTextEnabled = enabled; |
524 | } |
525 | |
526 | void KRichTextWidgetPrivate::_k_setListStyle(int index) |
527 | { |
528 | Q_Q(KRichTextWidget); |
529 | |
530 | q->setListStyle(index); |
531 | updateMiscActions(); |
532 | } |
533 | |
534 | void KRichTextWidgetPrivate::_k_setHeadingLevel(int level) |
535 | { |
536 | Q_Q(KRichTextWidget); |
537 | |
538 | q->setHeadingLevel(level); |
539 | updateMiscActions(); |
540 | } |
541 | |
542 | void KRichTextWidgetPrivate::updateCharFormatActions(const QTextCharFormat &format) |
543 | { |
544 | Q_Q(KRichTextWidget); |
545 | |
546 | QFont f = format.font(); |
547 | |
548 | if (richTextSupport & KRichTextWidget::SupportFontFamily) { |
549 | action_font_family->setFont(f.family()); |
550 | } |
551 | if (richTextSupport & KRichTextWidget::SupportFontSize) { |
552 | if (f.pointSize() > 0) { |
553 | action_font_size->setFontSize(f.pointSize()); |
554 | } |
555 | } |
556 | |
557 | if (richTextSupport & KRichTextWidget::SupportBold) { |
558 | action_text_bold->setChecked(f.bold()); |
559 | } |
560 | |
561 | if (richTextSupport & KRichTextWidget::SupportItalic) { |
562 | action_text_italic->setChecked(f.italic()); |
563 | } |
564 | |
565 | if (richTextSupport & KRichTextWidget::SupportUnderline) { |
566 | action_text_underline->setChecked(f.underline()); |
567 | } |
568 | |
569 | if (richTextSupport & KRichTextWidget::SupportStrikeOut) { |
570 | action_text_strikeout->setChecked(f.strikeOut()); |
571 | } |
572 | |
573 | if (richTextSupport & KRichTextWidget::SupportSuperScriptAndSubScript) { |
574 | QTextCharFormat::VerticalAlignment vAlign = format.verticalAlignment(); |
575 | action_text_superscript->setChecked(vAlign == QTextCharFormat::AlignSuperScript); |
576 | action_text_subscript->setChecked(vAlign == QTextCharFormat::AlignSubScript); |
577 | } |
578 | } |
579 | |
580 | void KRichTextWidgetPrivate::updateMiscActions() |
581 | { |
582 | Q_Q(KRichTextWidget); |
583 | |
584 | if (richTextSupport & KRichTextWidget::SupportAlignment) { |
585 | Qt::Alignment a = q->alignment(); |
586 | if (a & Qt::AlignLeft) { |
587 | action_align_left->setChecked(true); |
588 | } else if (a & Qt::AlignHCenter) { |
589 | action_align_center->setChecked(true); |
590 | } else if (a & Qt::AlignRight) { |
591 | action_align_right->setChecked(true); |
592 | } else if (a & Qt::AlignJustify) { |
593 | action_align_justify->setChecked(true); |
594 | } |
595 | } |
596 | |
597 | if (richTextSupport & KRichTextWidget::SupportChangeListStyle) { |
598 | if (q->textCursor().currentList()) { |
599 | action_list_style->setCurrentItem(-q->textCursor().currentList()->format().style()); |
600 | } else { |
601 | action_list_style->setCurrentItem(0); |
602 | } |
603 | } |
604 | |
605 | if (richTextSupport & KRichTextWidget::SupportIndentLists) { |
606 | if (richTextEnabled) { |
607 | action_list_indent->setEnabled(q->canIndentList()); |
608 | } else { |
609 | action_list_indent->setEnabled(false); |
610 | } |
611 | } |
612 | |
613 | if (richTextSupport & KRichTextWidget::SupportDedentLists) { |
614 | if (richTextEnabled) { |
615 | action_list_dedent->setEnabled(q->canDedentList()); |
616 | } else { |
617 | action_list_dedent->setEnabled(false); |
618 | } |
619 | } |
620 | |
621 | if (richTextSupport & KRichTextWidget::SupportDirection) { |
622 | const Qt::LayoutDirection direction = q->textCursor().blockFormat().layoutDirection(); |
623 | action_direction_ltr->setChecked(direction == Qt::LeftToRight); |
624 | action_direction_rtl->setChecked(direction == Qt::RightToLeft); |
625 | } |
626 | |
627 | if (richTextSupport & KRichTextWidget::SupportHeading) { |
628 | action_heading_level->setCurrentItem(q->textCursor().blockFormat().headingLevel()); |
629 | } |
630 | } |
631 | |
632 | void KRichTextWidgetPrivate::_k_setTextForegroundColor() |
633 | { |
634 | Q_Q(KRichTextWidget); |
635 | |
636 | const QColor currentColor = q->textColor(); |
637 | const QColor defaultColor = KColorScheme(QPalette::Active, KColorScheme::View).foreground().color(); |
638 | |
639 | const QColor selectedColor = QColorDialog::getColor(initial: currentColor.isValid() ? currentColor : defaultColor, parent: q); |
640 | |
641 | if (!selectedColor.isValid() && !currentColor.isValid()) { |
642 | q->setTextForegroundColor(defaultColor); |
643 | } else if (selectedColor.isValid()) { |
644 | q->setTextForegroundColor(selectedColor); |
645 | } |
646 | } |
647 | |
648 | void KRichTextWidgetPrivate::_k_setTextBackgroundColor() |
649 | { |
650 | Q_Q(KRichTextWidget); |
651 | |
652 | QTextCharFormat fmt = q->textCursor().charFormat(); |
653 | const QColor currentColor = fmt.background().color(); |
654 | const QColor defaultColor = KColorScheme(QPalette::Active, KColorScheme::View).foreground().color(); |
655 | |
656 | const QColor selectedColor = QColorDialog::getColor(initial: currentColor.isValid() ? currentColor : defaultColor, parent: q); |
657 | |
658 | if (!selectedColor.isValid() && !currentColor.isValid()) { |
659 | q->setTextBackgroundColor(defaultColor); |
660 | } else if (selectedColor.isValid()) { |
661 | q->setTextBackgroundColor(selectedColor); |
662 | } |
663 | } |
664 | |
665 | void KRichTextWidgetPrivate::_k_manageLink() |
666 | { |
667 | Q_Q(KRichTextWidget); |
668 | |
669 | q->selectLinkText(); |
670 | KLinkDialog *linkDialog = new KLinkDialog(q); |
671 | linkDialog->setLinkText(q->currentLinkText()); |
672 | linkDialog->setLinkUrl(q->currentLinkUrl()); |
673 | linkDialog->setAttribute(Qt::WA_DeleteOnClose); |
674 | |
675 | QObject::connect(sender: linkDialog, signal: &QDialog::accepted, context: linkDialog, slot: [linkDialog, this]() { |
676 | Q_Q(KRichTextWidget); |
677 | q->updateLink(linkUrl: linkDialog->linkUrl(), linkText: linkDialog->linkText()); |
678 | }); |
679 | |
680 | linkDialog->show(); |
681 | } |
682 | |
683 | void KRichTextWidget::mouseReleaseEvent(QMouseEvent *event) |
684 | { |
685 | Q_D(KRichTextWidget); |
686 | |
687 | if (d->painterActive) { |
688 | // If the painter is active, paint the selection with the |
689 | // correct format. |
690 | if (textCursor().hasSelection()) { |
691 | QTextCursor c = textCursor(); |
692 | c.setCharFormat(d->painterFormat); |
693 | setTextCursor(c); |
694 | } |
695 | d->painterActive = false; |
696 | d->action_format_painter->setChecked(false); |
697 | } |
698 | KRichTextEdit::mouseReleaseEvent(e: event); |
699 | } |
700 | |
701 | void KRichTextWidgetPrivate::_k_formatPainter(bool active) |
702 | { |
703 | Q_Q(KRichTextWidget); |
704 | |
705 | if (active) { |
706 | painterFormat = q->currentCharFormat(); |
707 | painterActive = true; |
708 | q->viewport()->setCursor(QCursor(QIcon::fromTheme(QStringLiteral("draw-brush" )).pixmap(w: 32, h: 32), 0, 32)); |
709 | } else { |
710 | painterFormat = QTextCharFormat(); |
711 | painterActive = false; |
712 | q->viewport()->setCursor(Qt::IBeamCursor); |
713 | } |
714 | } |
715 | |
716 | void KRichTextWidget::updateActionStates() |
717 | { |
718 | Q_D(KRichTextWidget); |
719 | |
720 | d->updateMiscActions(); |
721 | d->updateCharFormatActions(format: currentCharFormat()); |
722 | } |
723 | |
724 | #include "moc_krichtextwidget.cpp" |
725 | |