qTox  Version: nightly | Commit: bc751c8e1cac455f9690654fcfe0f560d2d7dfdd
text.cpp
Go to the documentation of this file.
1 /*
2  Copyright © 2014-2019 by The qTox Project Contributors
3 
4  This file is part of qTox, a Qt-based graphical interface for Tox.
5 
6  qTox is libre software: you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation, either version 3 of the License, or
9  (at your option) any later version.
10 
11  qTox 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 qTox. If not, see <http://www.gnu.org/licenses/>.
18 */
19 
20 #include "text.h"
21 #include "../documentcache.h"
22 
23 #include <QAbstractTextDocumentLayout>
24 #include <QApplication>
25 #include <QDebug>
26 #include <QDesktopServices>
27 #include <QFontMetrics>
28 #include <QGraphicsSceneMouseEvent>
29 #include <QPainter>
30 #include <QPalette>
31 #include <QTextBlock>
32 #include <QTextFragment>
33 
34 Text::Text(const QString& txt, const QFont& font, bool enableElide, const QString& rwText,
35  const TextType& type, const QColor& custom)
36  : rawText(rwText)
37  , elide(enableElide)
38  , defFont(font)
39  , defStyleSheet(Style::getStylesheet(QStringLiteral("chatArea/innerStyle.css"), font))
40  , textType(type)
41  , customColor(custom)
42 {
43  color = textColor();
44  setText(txt);
45  setAcceptedMouseButtons(Qt::LeftButton);
46  setAcceptHoverEvents(true);
47 }
48 
50 {
51  if (doc)
53 }
54 
55 void Text::setText(const QString& txt)
56 {
57  text = txt;
58  dirty = true;
59 }
60 
61 void Text::selectText(const QString& txt, const std::pair<int, int>& point)
62 {
63  regenerate();
64 
65  if (!doc) {
66  return;
67  }
68 
69  auto cursor = doc->find(txt, point.first);
70 
71  selectText(cursor, point);
72 }
73 
74 void Text::selectText(const QRegularExpression &exp, const std::pair<int, int>& point)
75 {
76  regenerate();
77 
78  if (!doc) {
79  return;
80  }
81 
82  auto cursor = doc->find(exp, point.first);
83 
84  selectText(cursor, point);
85 }
86 
88 {
89  dirty = true;
90  regenerate();
91  update();
92 }
93 
94 void Text::setWidth(qreal w)
95 {
96  width = w;
97  dirty = true;
98 
99  regenerate();
100 }
101 
102 void Text::selectionMouseMove(QPointF scenePos)
103 {
104  if (!doc)
105  return;
106 
107  int cur = cursorFromPos(scenePos);
108  if (cur >= 0) {
109  selectionEnd = cur;
111  }
112 
113  update();
114 }
115 
116 void Text::selectionStarted(QPointF scenePos)
117 {
118  int cur = cursorFromPos(scenePos);
119  if (cur >= 0) {
120  selectionEnd = cur;
121  selectionAnchor = cur;
122  }
123 }
124 
126 {
127  selectedText.clear();
128  selectedText.squeeze();
129 
130  // Do not reset selectionAnchor!
131  selectionEnd = -1;
132 
133  update();
134 }
135 
136 void Text::selectionDoubleClick(QPointF scenePos)
137 {
138  if (!doc)
139  return;
140 
141  int cur = cursorFromPos(scenePos);
142 
143  if (cur >= 0) {
144  QTextCursor cursor(doc);
145  cursor.setPosition(cur);
146  cursor.select(QTextCursor::WordUnderCursor);
147 
148  selectionAnchor = cursor.selectionStart();
149  selectionEnd = cursor.selectionEnd();
150 
152  }
153 
154  update();
155 }
156 
157 void Text::selectionTripleClick(QPointF scenePos)
158 {
159  if (!doc)
160  return;
161 
162  int cur = cursorFromPos(scenePos);
163 
164  if (cur >= 0) {
165  QTextCursor cursor(doc);
166  cursor.setPosition(cur);
167  cursor.select(QTextCursor::BlockUnderCursor);
168 
169  selectionAnchor = cursor.selectionStart();
170  selectionEnd = cursor.selectionEnd();
171 
172  if (cursor.block().isValid() && cursor.block().blockNumber() != 0)
173  selectionAnchor++;
174 
176  }
177 
178  update();
179 }
180 
181 void Text::selectionFocusChanged(bool focusIn)
182 {
183  selectionHasFocus = focusIn;
184  update();
185 }
186 
187 bool Text::isOverSelection(QPointF scenePos) const
188 {
189  int cur = cursorFromPos(scenePos);
190  if (getSelectionStart() < cur && getSelectionEnd() >= cur)
191  return true;
192 
193  return false;
194 }
195 
196 QString Text::getSelectedText() const
197 {
198  return selectedText;
199 }
200 
201 void Text::fontChanged(const QFont& font)
202 {
203  defFont = font;
204 }
205 
206 QRectF Text::boundingRect() const
207 {
208  return QRectF(QPointF(0, 0), size);
209 }
210 
211 void Text::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
212 {
213  Q_UNUSED(option)
214  Q_UNUSED(widget)
215 
216  if (!doc)
217  return;
218 
219  painter->setClipRect(boundingRect());
220 
221  // draw selection
222  QAbstractTextDocumentLayout::PaintContext ctx;
223  QAbstractTextDocumentLayout::Selection sel;
224 
225  if (hasSelection()) {
226  sel.cursor = QTextCursor(doc);
227  sel.cursor.setPosition(getSelectionStart());
228  sel.cursor.setPosition(getSelectionEnd(), QTextCursor::KeepAnchor);
229  }
230 
231  const QColor selectionColor = Style::getColor(Style::SelectText);
232  sel.format.setBackground(selectionColor.lighter(selectionHasFocus ? 100 : 160));
233  sel.format.setForeground(selectionHasFocus ? Qt::white : Qt::black);
234 
235  ctx.selections.append(sel);
236  ctx.palette.setColor(QPalette::Text, color);
237 
238  // draw text
239  doc->documentLayout()->draw(painter, ctx);
240 }
241 
242 void Text::visibilityChanged(bool visible)
243 {
244  keepInMemory = visible;
245 
246  regenerate();
247  update();
248 }
249 
251 {
252  defStyleSheet = Style::getStylesheet(QStringLiteral("chatArea/innerStyle.css"), defFont);
253  color = textColor();
254  dirty = true;
255  regenerate();
256  update();
257 }
258 
259 qreal Text::getAscent() const
260 {
261  return ascent;
262 }
263 
264 void Text::mousePressEvent(QGraphicsSceneMouseEvent* event)
265 {
266  if (event->button() == Qt::LeftButton)
267  event->accept(); // grabber
268 }
269 
270 void Text::mouseReleaseEvent(QGraphicsSceneMouseEvent* event)
271 {
272  if (!doc)
273  return;
274 
275  QString anchor = doc->documentLayout()->anchorAt(event->pos());
276 
277  // open anchor in browser
278  if (!anchor.isEmpty())
279  QDesktopServices::openUrl(anchor);
280 }
281 
282 void Text::hoverMoveEvent(QGraphicsSceneHoverEvent* event)
283 {
284  if (!doc)
285  return;
286 
287  QString anchor = doc->documentLayout()->anchorAt(event->pos());
288 
289  if (anchor.isEmpty())
290  setCursor(Qt::IBeamCursor);
291  else
292  setCursor(Qt::PointingHandCursor);
293 
294  // tooltip
295  setToolTip(extractImgTooltip(cursorFromPos(event->scenePos(), false)));
296 }
297 
298 QString Text::getText() const
299 {
300  return rawText;
301 }
302 
308 QString Text::getLinkAt(QPointF scenePos) const
309 {
310  QTextCursor cursor(doc);
311  cursor.setPosition(cursorFromPos(scenePos));
312  return cursor.charFormat().anchorHref();
313 }
314 
316 {
317  if (!doc) {
319  dirty = true;
320  }
321 
322  if (dirty) {
323  doc->setDefaultFont(defFont);
324 
325  if (elide) {
326  QFontMetrics metrics = QFontMetrics(defFont);
327  QString elidedText = metrics.elidedText(text, Qt::ElideRight, qRound(width));
328 
329  doc->setPlainText(elidedText);
330  } else {
331  doc->setDefaultStyleSheet(defStyleSheet);
332  doc->setHtml(text);
333  }
334 
335  // wrap mode
336  QTextOption opt;
337  opt.setWrapMode(elide ? QTextOption::NoWrap : QTextOption::WrapAtWordBoundaryOrAnywhere);
338  doc->setDefaultTextOption(opt);
339 
340  // width
341  doc->setTextWidth(width);
342  doc->documentLayout()->update();
343 
344  // update ascent
345  if (doc->firstBlock().layout()->lineCount() > 0)
346  ascent = doc->firstBlock().layout()->lineAt(0).ascent();
347 
348  // let the scene know about our change in size
349  if (size != idealSize())
350  prepareGeometryChange();
351 
352  // get the new width and height
353  size = idealSize();
354 
355  dirty = false;
356  }
357 
358  // if we are not visible -> free mem
359  if (!keepInMemory)
360  freeResources();
361 }
362 
364 {
366  doc = nullptr;
367 }
368 
370 {
371  if (doc)
372  return doc->size();
373 
374  return size;
375 }
376 
377 int Text::cursorFromPos(QPointF scenePos, bool fuzzy) const
378 {
379  if (doc)
380  return doc->documentLayout()->hitTest(mapFromScene(scenePos),
381  fuzzy ? Qt::FuzzyHit : Qt::ExactHit);
382 
383  return -1;
384 }
385 
387 {
388  return qMax(selectionAnchor, selectionEnd);
389 }
390 
392 {
393  return qMin(selectionAnchor, selectionEnd);
394 }
395 
396 bool Text::hasSelection() const
397 {
398  return selectionEnd >= 0;
399 }
400 
401 QString Text::extractSanitizedText(int from, int to) const
402 {
403  if (!doc)
404  return "";
405 
406  QString txt;
407 
408  QTextBlock begin = doc->findBlock(from);
409  QTextBlock end = doc->findBlock(to);
410  for (QTextBlock block = begin; block != end.next() && block.isValid(); block = block.next()) {
411  for (QTextBlock::Iterator itr = block.begin(); itr != block.end(); ++itr) {
412  int pos = itr.fragment().position(); // fragment position -> position of the first
413  // character in the fragment
414 
415  if (itr.fragment().charFormat().isImageFormat()) {
416  QTextImageFormat imgFmt = itr.fragment().charFormat().toImageFormat();
417  QString key = imgFmt.name(); // img key (eg. key::D for :D)
418  QString rune = key.mid(4);
419 
420  if (pos >= from && pos < to) {
421  txt += rune;
422  ++pos;
423  }
424  } else {
425  for (QChar c : itr.fragment().text()) {
426  if (pos >= from && pos < to)
427  txt += c;
428 
429  ++pos;
430  }
431  }
432  }
433 
434  txt += '\n';
435  }
436 
437  txt.chop(1);
438 
439  return txt;
440 }
441 
442 QString Text::extractImgTooltip(int pos) const
443 {
444  for (QTextBlock::Iterator itr = doc->firstBlock().begin(); itr != doc->firstBlock().end(); ++itr) {
445  if (itr.fragment().contains(pos) && itr.fragment().charFormat().isImageFormat()) {
446  QTextImageFormat imgFmt = itr.fragment().charFormat().toImageFormat();
447  return imgFmt.toolTip();
448  }
449  }
450 
451  return QString();
452 }
453 
454 void Text::selectText(QTextCursor& cursor, const std::pair<int, int>& point)
455 {
456  if (!cursor.isNull()) {
457  cursor.beginEditBlock();
458  cursor.setPosition(point.first);
459  cursor.setPosition(point.first + point.second, QTextCursor::KeepAnchor);
460  cursor.endEditBlock();
461 
462  QTextCharFormat format;
463  format.setBackground(QBrush(Style::getColor(Style::SearchHighlighted)));
464  cursor.mergeCharFormat(format);
465 
466  regenerate();
467  update();
468  }
469 }
470 
471 QColor Text::textColor() const
472 {
473  QColor c = Style::getColor(Style::MainText);
474  if (textType == ACTION) {
476  } else if (textType == CUSTOM) {
477  c = customColor;
478  }
479 
480  return c;
481 }
Text::selectionAnchor
int selectionAnchor
Definition: text.h:105
Text::selectionCleared
void selectionCleared() final
Definition: text.cpp:125
DocumentCache::getInstance
static DocumentCache & getInstance()
Returns the singleton instance.
Definition: documentcache.cpp:48
Text::extractSanitizedText
QString extractSanitizedText(int from, int to) const
Definition: text.cpp:401
Text::visibilityChanged
void visibilityChanged(bool keepInMemory) final
Definition: text.cpp:242
Text::ACTION
@ ACTION
Definition: text.h:37
Style::Action
@ Action
Definition: style.h:48
Text::width
qreal width
Definition: text.h:91
Text::textColor
QColor textColor() const
Definition: text.cpp:471
Text::selectionStarted
void selectionStarted(QPointF scenePos) final
Definition: text.cpp:116
Text::regenerate
void regenerate()
Definition: text.cpp:315
Text::color
QColor color
Definition: text.h:110
Text::defStyleSheet
QString defStyleSheet
Definition: text.h:108
Text::selectionMouseMove
void selectionMouseMove(QPointF scenePos) final
Definition: text.cpp:102
DocumentCache::pop
QTextDocument * pop()
Definition: documentcache.cpp:29
Text::rawText
QString rawText
Definition: text.h:98
Text::cursorFromPos
int cursorFromPos(QPointF scenePos, bool fuzzy=true) const
Definition: text.cpp:377
Text::selectText
void selectText(const QString &txt, const std::pair< int, int > &point)
Definition: text.cpp:61
Text::getSelectedText
QString getSelectedText() const final
Definition: text.cpp:196
Text::paint
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) final
Definition: text.cpp:211
Text::doc
QTextDocument * doc
Definition: text.h:89
Text::selectionTripleClick
void selectionTripleClick(QPointF scenePos) final
Definition: text.cpp:157
Style::SearchHighlighted
@ SearchHighlighted
Definition: style.h:50
Text::idealSize
virtual QSizeF idealSize()
Definition: text.cpp:369
Text::text
QString text
Definition: text.h:97
Text::customColor
QColor customColor
Definition: text.h:111
Text::setWidth
void setWidth(qreal width) final
Definition: text.cpp:94
Text::~Text
virtual ~Text()
Definition: text.cpp:49
Text::dirty
bool dirty
Definition: text.h:102
Style
Definition: style.h:28
Text::selectionDoubleClick
void selectionDoubleClick(QPointF scenePos) final
Definition: text.cpp:136
Text::extractImgTooltip
QString extractImgTooltip(int pos) const
Definition: text.cpp:442
Text::selectedText
QString selectedText
Definition: text.h:99
Text::ascent
qreal ascent
Definition: text.h:106
Text::selectionHasFocus
bool selectionHasFocus
Definition: text.h:103
text.h
Text::freeResources
void freeResources()
Definition: text.cpp:363
Text::selectionEnd
int selectionEnd
Definition: text.h:104
Style::SelectText
@ SelectText
Definition: style.h:51
Style::getColor
static QColor getColor(ColorPalette entry)
Definition: style.cpp:209
Text::getText
QString getText() const final
Definition: text.cpp:298
Text::size
QSizeF size
Definition: text.h:90
Text::hoverMoveEvent
void hoverMoveEvent(QGraphicsSceneHoverEvent *event) final
Definition: text.cpp:282
Text::mousePressEvent
void mousePressEvent(QGraphicsSceneMouseEvent *event) final
Definition: text.cpp:264
Text::CUSTOM
@ CUSTOM
Definition: text.h:38
Text::TextType
TextType
Definition: text.h:34
Text::elide
bool elide
Definition: text.h:101
Text::reloadTheme
void reloadTheme() final
Definition: text.cpp:250
Style::getStylesheet
static const QString getStylesheet(const QString &filename, const QFont &baseFont=QFont())
Definition: style.cpp:165
Text::mouseReleaseEvent
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) final
Definition: text.cpp:270
Text::isOverSelection
bool isOverSelection(QPointF scenePos) const final
Definition: text.cpp:187
Style::MainText
@ MainText
Definition: style.h:37
Text::selectionFocusChanged
void selectionFocusChanged(bool focusIn) final
Definition: text.cpp:181
Text::getSelectionStart
int getSelectionStart() const
Definition: text.cpp:391
Text::boundingRect
QRectF boundingRect() const final
Definition: text.cpp:206
Text::fontChanged
void fontChanged(const QFont &font) final
Definition: text.cpp:201
Text::getLinkAt
QString getLinkAt(QPointF scenePos) const
Extracts the target of a link from the text at a given coordinate.
Definition: text.cpp:308
Text::Text
Text(const QString &txt="", const QFont &font=QFont(), bool enableElide=false, const QString &rawText=QString(), const TextType &type=NORMAL, const QColor &custom=Style::getColor(Style::MainText))
Definition: text.cpp:34
Text::getAscent
qreal getAscent() const final
Definition: text.cpp:259
Text::deselectText
void deselectText()
Definition: text.cpp:87
Text::setText
void setText(const QString &txt)
Definition: text.cpp:55
Text::getSelectionEnd
int getSelectionEnd() const
Definition: text.cpp:386
Text::textType
TextType textType
Definition: text.h:109
DocumentCache::push
void push(QTextDocument *doc)
Definition: documentcache.cpp:37
Text::defFont
QFont defFont
Definition: text.h:107
Text::hasSelection
bool hasSelection() const
Definition: text.cpp:396
Text::keepInMemory
bool keepInMemory
Definition: text.h:100