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