1 /**********************************************************************************************
2 Copyright (C) 2016 Christian Eichler <code@christian-eichler.de>
3
4 Copyright (C) 2012 Digia Plc and/or its subsidiaries <>
5 Contact: http://www.qt-project.org/legal
6
7 This program is free software: you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation, version 3 of the License.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with this program. If not, see <http://www.gnu.org/licenses/>.
18
19 **********************************************************************************************/
20
21
22 #include "CTextEditWidget.h"
23 #include "helpers/CSettings.h"
24 #include "helpers/Signals.h"
25 #include "widgets/CTemplateWidget.h"
26 #include "widgets/CTextEditWidgetSelMenu.h"
27
28 #include <QtWidgets>
29
CTextEditWidget(const QString & html,QWidget * parent)30 CTextEditWidget::CTextEditWidget(const QString& html, QWidget* parent)
31 : QDialog(parent)
32 {
33 SETTINGS;
34
35 bool pastePlain = cfg.value("TextEditWidget/pastePlain", false).toBool();
36
37 setupUi(this);
38
39 selectionWindow = new CTextEditWidgetSelMenu(this,
40 /* font style actions */ actionTextBold, actionTextItalic, actionTextUnderline,
41 /* copy/paste actions */ actionCut, actionCopy, actionPaste
42 );
43
44 QScrollBar* vbar = textEdit->verticalScrollBar();
45 connect(vbar, &QAbstractSlider::valueChanged, this, &CTextEditWidget::textEditScrolled);
46
47 toolBold->setDefaultAction (actionTextBold);
48 toolItalic->setDefaultAction(actionTextItalic);
49 toolUnder->setDefaultAction (actionTextUnderline);
50
51 connect(actionTextBold, &QAction::triggered, this, &CTextEditWidget::textBold);
52 connect(actionTextItalic, &QAction::triggered, this, &CTextEditWidget::textItalic);
53 connect(actionTextUnderline, &QAction::triggered, this, &CTextEditWidget::textUnderline);
54
55 QActionGroup* grp = new QActionGroup(this);
56 grp->addAction(actionAlignLeft);
57 grp->addAction(actionAlignRight);
58 grp->addAction(actionAlignCenter);
59 grp->addAction(actionAlignJustify);
60 connect(grp, &QActionGroup::triggered, this, &CTextEditWidget::textAlign);
61
62 toolLeft->setDefaultAction(actionAlignLeft);
63 toolCenter->setDefaultAction(actionAlignCenter);
64 toolRight->setDefaultAction(actionAlignRight);
65 toolBlock->setDefaultAction(actionAlignJustify);
66
67 defaultFont = textEdit->font();
68
69 QPixmap pix(24, 24);
70 pix.fill(Qt::black);
71 actionTextColor = new QAction(pix, tr("&Color..."), this);
72 connect(actionTextColor, &QAction::triggered, this, &CTextEditWidget::textColor);
73 toolColor->setDefaultAction(actionTextColor);
74
75 connect(comboStyle, static_cast<void (QComboBox::*)(int)>(&QComboBox::activated), this, &CTextEditWidget::textStyle);
76
77 connect(comboFont, &QFontComboBox::currentFontChanged, textEdit, &QTextEdit::setCurrentFont);
78 connect(spinFontSize, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), textEdit, &QTextEdit::setFontPointSize);
79
80 connect(textEdit, &QTextEdit::currentCharFormatChanged, this, &CTextEditWidget::currentCharFormatChanged);
81 connect(textEdit, &QTextEdit::cursorPositionChanged, this, &CTextEditWidget::cursorPositionChanged);
82 connect(textEdit, &QTextEdit::textChanged, this, &CTextEditWidget::cursorPositionChanged);
83 connect(textEdit, &QTextEdit::selectionChanged, this, &CTextEditWidget::selectionChanged);
84
85 textEdit->setHtml(html);
86 textEdit->setFocus();
87
88 fontChanged(textEdit->font());
89 colorChanged(textEdit->textColor());
90 alignmentChanged(textEdit->alignment());
91
92 toolInsertFromTemplate->setDefaultAction(actionInsertFromTemplate);
93 toolUndo->setDefaultAction(actionUndo);
94 toolRedo->setDefaultAction(actionRedo);
95 toolCut->setDefaultAction(actionCut);
96 toolCopy->setDefaultAction(actionCopy);
97 toolPaste->setDefaultAction(actionPaste);
98
99 QMenu* menu = new QMenu(this);
100 menu->addAction(actionPastePlain);
101 menu->addAction(actionPasteNormal);
102 toolPaste->setMenu(menu);
103
104 /* Setup contextmenu for textEdit */
105 menuTextEdit = new QMenu(this);
106 menuTextEdit->addAction(actionInsertFromTemplate);
107 menuTextEdit->addSeparator();
108 menuTextEdit->addAction(actionUndo);
109 menuTextEdit->addAction(actionRedo);
110 menuTextEdit->addSeparator();
111 menuTextEdit->addAction(actionCut);
112 menuTextEdit->addAction(actionCopy);
113 menuTextEdit->addAction(actionPaste);
114 menuTextEdit->addAction(actionDelete);
115 menuTextEdit->addSeparator();
116
117 removeFormat = new QMenu(tr("Reset format"), this);
118 {
119 menuTextEdit->addMenu(removeFormat);
120 removeFormat->addAction(actionResetFont);
121 removeFormat->addAction(actionResetLayout);
122 }
123
124 connect(actionResetFont, &QAction::triggered, this, &CTextEditWidget::resetFont);
125 connect(actionResetLayout, &QAction::triggered, this, &CTextEditWidget::resetLayout);
126
127 menuTextEdit->addAction(actionSelectAll);
128
129 actionPaste->setEnabled(!QApplication::clipboard()->text().isEmpty());
130 actionPastePlain->setEnabled(!QApplication::clipboard()->text().isEmpty());
131 actionUndo->setEnabled(textEdit->document()->isUndoAvailable());
132 actionRedo->setEnabled(textEdit->document()->isRedoAvailable());
133
134 connect(textEdit->document(), &QTextDocument::undoAvailable, actionUndo, &QAction::setEnabled);
135 connect(textEdit->document(), &QTextDocument::redoAvailable, actionRedo, &QAction::setEnabled);
136
137 connect(actionInsertFromTemplate, &QAction::triggered, this, &CTextEditWidget::insertFromTemplate);
138 connect(actionUndo, &QAction::triggered, textEdit, &QTextEdit::undo);
139 connect(actionRedo, &QAction::triggered, textEdit, &QTextEdit::redo);
140
141 actionCut->setEnabled(false);
142 actionCopy->setEnabled(false);
143
144 QActionGroup* pasteGroup = new QActionGroup(this);
145 actionPastePlain->setChecked(pastePlain);
146 actionPasteNormal->setChecked(!pastePlain);
147 pasteGroup->addAction(actionPastePlain);
148 pasteGroup->addAction(actionPasteNormal);
149 connect(pasteGroup, &QActionGroup::triggered, this, &CTextEditWidget::pasteMode);
150
151 pasteMode(pastePlain ? actionPastePlain : actionPasteNormal);
152
153 connect(actionCut, &QAction::triggered, textEdit, &QTextEdit::cut);
154 connect(actionCopy, &QAction::triggered, textEdit, &QTextEdit::copy);
155 connect(actionSelectAll, &QAction::triggered, textEdit, &QTextEdit::selectAll);
156 connect(actionPaste, &QAction::triggered, textEdit, &CTextEdit::paste);
157 connect(actionDelete, &QAction::triggered, this, &CTextEditWidget::deleteSelected);
158 connect(textEdit, &QTextEdit::customContextMenuRequested, this, &CTextEditWidget::customContextMenuRequested);
159 connect(textEdit, &QTextEdit::copyAvailable, actionCut, &QAction::setEnabled);
160 connect(textEdit, &QTextEdit::copyAvailable, actionCopy, &QAction::setEnabled);
161
162 connect(QApplication::clipboard(), &QClipboard::dataChanged, this, &CTextEditWidget::clipboardDataChanged);
163 }
164
~CTextEditWidget()165 CTextEditWidget::~CTextEditWidget()
166 {
167 SETTINGS;
168 cfg.setValue("TextEditWidget/pastePlain", actionPastePlain->isChecked());
169 }
170
getHtml()171 QString CTextEditWidget::getHtml()
172 {
173 QString str = textEdit->toHtml();
174 QRegExp re(".*(\\<body.*body\\>).*");
175 if(re.exactMatch(str))
176 {
177 str = re.cap(1);
178
179 QRegExp re1("<body.*>");
180 re1.setMinimal(true);
181 str = str.replace("body>", "div>").replace(re1, "<div>");
182 }
183
184 return str;
185 }
186
187
textBold()188 void CTextEditWidget::textBold()
189 {
190 QTextCharFormat fmt;
191 fmt.setFontWeight(actionTextBold->isChecked() ? QFont::Bold : QFont::Normal);
192 mergeFormatOnWordOrSelection(fmt);
193 }
194
195
textUnderline()196 void CTextEditWidget::textUnderline()
197 {
198 QTextCharFormat fmt;
199 fmt.setFontUnderline(actionTextUnderline->isChecked());
200 mergeFormatOnWordOrSelection(fmt);
201 }
202
203
textItalic()204 void CTextEditWidget::textItalic()
205 {
206 QTextCharFormat fmt;
207 fmt.setFontItalic(actionTextItalic->isChecked());
208 mergeFormatOnWordOrSelection(fmt);
209 }
210
211
textAlign(QAction * a)212 void CTextEditWidget::textAlign(QAction* a)
213 {
214 if (a == actionAlignLeft)
215 {
216 textEdit->setAlignment(Qt::AlignLeft);
217 }
218 else if (a == actionAlignCenter)
219 {
220 textEdit->setAlignment(Qt::AlignHCenter);
221 }
222 else if (a == actionAlignRight)
223 {
224 textEdit->setAlignment(Qt::AlignRight);
225 }
226 else if (a == actionAlignJustify)
227 {
228 textEdit->setAlignment(Qt::AlignJustify);
229 }
230 }
231
232
233
textStyle(int styleIndex)234 void CTextEditWidget::textStyle(int styleIndex)
235 {
236 if (styleIndex > 0)
237 {
238 QTextCursor cursor = textEdit->textCursor();
239 QTextListFormat::Style style = QTextListFormat::ListDisc;
240
241 static QTextListFormat::Style indexToFormat[] =
242 {
243 QTextListFormat::ListDisc,
244 QTextListFormat::ListCircle,
245 QTextListFormat::ListSquare,
246 QTextListFormat::ListDecimal,
247 QTextListFormat::ListLowerAlpha,
248 QTextListFormat::ListUpperAlpha,
249 QTextListFormat::ListLowerRoman,
250 QTextListFormat::ListUpperRoman
251 };
252
253 if( (unsigned) styleIndex <= sizeof(indexToFormat) / sizeof(QTextListFormat::Style))
254 {
255 style = indexToFormat[styleIndex - 1];
256 }
257
258 cursor.beginEditBlock();
259
260 QTextBlockFormat blockFmt = cursor.blockFormat();
261
262 QTextListFormat listFmt;
263
264 if (cursor.currentList())
265 {
266 listFmt = cursor.currentList()->format();
267 }
268 else
269 {
270 listFmt.setIndent(blockFmt.indent() + 1);
271 blockFmt.setIndent(0);
272 cursor.setBlockFormat(blockFmt);
273 }
274
275 listFmt.setStyle(style);
276
277 cursor.createList(listFmt);
278
279 cursor.endEditBlock();
280 }
281 else
282 {
283 resetLayout();
284 }
285 }
286
resetLayout()287 void CTextEditWidget::resetLayout()
288 {
289 textEdit->textCursor().setBlockFormat(QTextBlockFormat());
290 }
291
resetFont()292 void CTextEditWidget::resetFont()
293 {
294 QTextCharFormat fmt;
295 fmt.setFontUnderline(false);
296 fmt.setFontWeight(QFont::Normal);
297 fmt.setFontItalic(false);
298 fmt.setForeground(QColor());
299
300 fmt.setFont(defaultFont);
301 fmt.setFontPointSize(defaultFont.pointSizeF());
302
303 QTextCursor cursor = textEdit->textCursor();
304 if (!cursor.hasSelection())
305 {
306 cursor.select(QTextCursor::WordUnderCursor);
307 }
308 cursor.setCharFormat(fmt);
309
310 fontChanged(defaultFont);
311 colorChanged(QColor());
312 }
313
textColor()314 void CTextEditWidget::textColor()
315 {
316 QColor col = QColorDialog::getColor(textEdit->textColor(), this);
317 if (!col.isValid())
318 {
319 return;
320 }
321 QTextCharFormat fmt;
322 fmt.setForeground(col);
323 mergeFormatOnWordOrSelection(fmt);
324 colorChanged(col);
325 }
326
327
mergeFormatOnWordOrSelection(const QTextCharFormat & format)328 void CTextEditWidget::mergeFormatOnWordOrSelection(const QTextCharFormat& format)
329 {
330 QTextCursor cursor = textEdit->textCursor();
331 if (!cursor.hasSelection())
332 {
333 cursor.select(QTextCursor::WordUnderCursor);
334 }
335 cursor.mergeCharFormat(format);
336 textEdit->mergeCurrentCharFormat(format);
337 }
338
339
fontChanged(const QFont & f)340 void CTextEditWidget::fontChanged(const QFont& f)
341 {
342 actionTextBold->setChecked(f.bold());
343 actionTextItalic->setChecked(f.italic());
344 actionTextUnderline->setChecked(f.underline());
345 }
346
347
colorChanged(const QColor & c)348 void CTextEditWidget::colorChanged(const QColor& c)
349 {
350 QPixmap pix(16, 16);
351 pix.fill(c);
352 actionTextColor->setIcon(pix);
353 }
354
355
alignmentChanged(Qt::Alignment a)356 void CTextEditWidget::alignmentChanged(Qt::Alignment a)
357 {
358 if (a & Qt::AlignLeft)
359 {
360 actionAlignLeft->setChecked(true);
361 }
362 else if (a & Qt::AlignHCenter)
363 {
364 actionAlignCenter->setChecked(true);
365 }
366 else if (a & Qt::AlignRight)
367 {
368 actionAlignRight->setChecked(true);
369 }
370 else if (a & Qt::AlignJustify)
371 {
372 actionAlignJustify->setChecked(true);
373 }
374 }
375
376
currentCharFormatChanged(const QTextCharFormat & format)377 void CTextEditWidget::currentCharFormatChanged(const QTextCharFormat& format)
378 {
379 fontChanged(format.font());
380 colorChanged(format.foreground().color());
381 }
382
383
cursorPositionChanged()384 void CTextEditWidget::cursorPositionChanged()
385 {
386 static QHash<QTextListFormat::Style, int> styleToIndex({
387 std::make_pair(QTextListFormat::ListDisc, 1),
388 std::make_pair(QTextListFormat::ListCircle, 2),
389 std::make_pair(QTextListFormat::ListSquare, 3),
390 std::make_pair(QTextListFormat::ListDecimal, 4),
391 std::make_pair(QTextListFormat::ListLowerAlpha, 5),
392 std::make_pair(QTextListFormat::ListUpperAlpha, 6),
393 std::make_pair(QTextListFormat::ListLowerRoman, 7),
394 std::make_pair(QTextListFormat::ListUpperRoman, 8)
395 });
396
397 alignmentChanged(textEdit->alignment());
398
399 int listStyleIndex = 0;
400
401 QTextCursor cursor = textEdit->textCursor();
402 if(cursor.currentList())
403 {
404 QTextListFormat::Style style = cursor.currentList()->format().style();
405
406 if(styleToIndex.contains(style))
407 {
408 listStyleIndex = styleToIndex[ cursor.currentList()->format().style() ];
409 }
410 }
411
412 X______________BlockAllSignals______________X(this);
413 comboStyle->setCurrentIndex(listStyleIndex);
414
415 const QFont& font = cursor.charFormat().font();
416 comboFont->setCurrentFont(font);
417
418 int pointSize = font.pointSize();
419
420 if(-1 == pointSize)
421 {
422 // some texts (if pasted from px. a browser) have their font size
423 // specified in pixels instead of points, so we need to convert that
424 QFontInfo info(font);
425 pointSize = info.pointSize();
426 }
427 spinFontSize->setValue(pointSize);
428
429 X_____________UnBlockAllSignals_____________X(this);
430 }
431
432
clipboardDataChanged()433 void CTextEditWidget::clipboardDataChanged()
434 {
435 actionPaste->setEnabled(!QApplication::clipboard()->text().isEmpty());
436 actionPastePlain->setEnabled(!QApplication::clipboard()->text().isEmpty());
437 }
438
439
selectionChanged()440 void CTextEditWidget::selectionChanged()
441 {
442 bool hasSel = textEdit->textCursor().hasSelection();
443
444 actionDelete->setEnabled (hasSel);
445 removeFormat->setEnabled (hasSel);
446 actionResetFont->setEnabled (hasSel);
447 actionResetLayout->setEnabled(hasSel);
448
449 updateSelectionWindow();
450 }
451
customContextMenuRequested()452 void CTextEditWidget::customContextMenuRequested()
453 {
454 menuTextEdit->exec(QCursor::pos());
455 }
456
deleteSelected()457 void CTextEditWidget::deleteSelected()
458 {
459 textEdit->insertPlainText(QString());
460 }
461
textEditScrolled()462 void CTextEditWidget::textEditScrolled()
463 {
464 updateSelectionWindow();
465 }
466
moveEvent(QMoveEvent * event)467 void CTextEditWidget::moveEvent(QMoveEvent* event)
468 {
469 updateSelectionWindow();
470 }
471
pasteMode(QAction * action)472 void CTextEditWidget::pasteMode(QAction* action)
473 {
474 textEdit->setPastePlain( action == actionPastePlain );
475
476 actionPaste->setIcon(action->icon());
477 }
478
event(QEvent * event)479 bool CTextEditWidget::event(QEvent* event)
480 {
481 if(event->type() == QEvent::WindowActivate)
482 {
483 updateSelectionWindow();
484 }
485 else if(event->type() == QEvent::WindowDeactivate)
486 {
487 selectionWindow->hide();
488 }
489 return QDialog::event(event);
490 }
491
updateSelectionWindow()492 void CTextEditWidget::updateSelectionWindow()
493 {
494 const QTextCursor& cursor = textEdit->textCursor();
495 const QRect& rect = textEdit->cursorRect();
496
497 // don't show the selctionWindow, if there is no selection or
498 // the cursor is not visible
499 if(cursor.hasSelection() && rect.y() >= 0 && rect.y() <= textEdit->height())
500 {
501 int dy = cursor.anchor() < cursor.position()
502 ? ( 6 + rect.height() )
503 : ( -6 - selectionWindow->height() );
504
505 int dx = -selectionWindow->width() / 2;
506
507 selectionWindow->move(textEdit->mapToGlobal(QPoint(rect.x(), rect.y())) + QPoint(dx, dy));
508 selectionWindow->show();
509 }
510 else
511 {
512 selectionWindow->hide();
513 }
514 }
515
516
insertFromTemplate()517 void CTextEditWidget::insertFromTemplate()
518 {
519 CTemplateWidget dlg(this);
520 if(dlg.exec() == QDialog::Accepted)
521 {
522 textEdit->insertHtml(dlg.text());
523 }
524 }
525