mirror of
https://github.com/qTox/qTox.git
synced 2024-03-22 14:00:36 +08:00
refactor: further improvement of message formatting
Brief list of changes: - names of some variables and constants were replaced for reading convenience; - URL-highlighting method moved to TextFormatter so URL's don't conflict with italic text formatting; - as I understand, in previous version 'file://' URL does not work because of bad regex. I fix this with help of wiki page that referenced in comment for old code. Important note: there are two equal 'file://' URL syntax: 'file:///...' and 'file://localhost/...' and the second one does NOT work in KDE but works in Gnome so that's not a bug Fix #4233
This commit is contained in:
parent
aabf34ebad
commit
3e2bfdd548
|
@ -55,7 +55,7 @@ ChatMessage::Ptr ChatMessage::createChatMessage(const QString& sender, const QSt
|
||||||
text = SmileyPack::getInstance().smileyfied(text);
|
text = SmileyPack::getInstance().smileyfied(text);
|
||||||
|
|
||||||
// quotes (green text)
|
// quotes (green text)
|
||||||
text = detectQuotes(detectAnchors(text), type);
|
text = detectQuotes(text, type);
|
||||||
|
|
||||||
// text styling
|
// text styling
|
||||||
Settings::StyleType styleType = Settings::getInstance().getStylePreference();
|
Settings::StyleType styleType = Settings::getInstance().getStylePreference();
|
||||||
|
@ -227,45 +227,6 @@ void ChatMessage::hideDate()
|
||||||
c->hide();
|
c->hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
QString ChatMessage::detectAnchors(const QString& str)
|
|
||||||
{
|
|
||||||
QString out = str;
|
|
||||||
|
|
||||||
// detect URIs
|
|
||||||
QRegExp exp(
|
|
||||||
"("
|
|
||||||
"(?:\\b)((www\\.)|(http[s]?|ftp)://)" // (protocol)://(printable - non-special character)
|
|
||||||
// http://ONEORMOREALHPA-DIGIT
|
|
||||||
"\\w+\\S+)" // any other character, lets domains and other
|
|
||||||
// ↓ link to a file, or samba share
|
|
||||||
// https://en.wikipedia.org/wiki/File_URI_scheme
|
|
||||||
"|(?:\\b)((file|smb)://)([\\S| ]*)"
|
|
||||||
"|(?:\\b)(tox:[a-zA-Z\\d]{76})" // link with full user address
|
|
||||||
"|(?:\\b)(mailto:\\S+@\\S+\\.\\S+)" //@mail link
|
|
||||||
"|(?:\\b)(tox:\\S+@\\S+)"); // starts with `tox` then : and only alpha-digits till the end
|
|
||||||
// also accepts tox:agilob@net as simplified TOX ID
|
|
||||||
|
|
||||||
int offset = 0;
|
|
||||||
while ((offset = exp.indexIn(out, offset)) != -1) {
|
|
||||||
QString url = exp.cap();
|
|
||||||
// If there's a trailing " it's a HTML attribute, e.g. a smiley img's title=":tox:"
|
|
||||||
if (url == "tox:\"") {
|
|
||||||
offset += url.length();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
QString htmledUrl;
|
|
||||||
// add scheme if not specified
|
|
||||||
if (exp.cap(2) == "www.")
|
|
||||||
htmledUrl = QString("<a href=\"http://%1\">%1</a>").arg(url);
|
|
||||||
else
|
|
||||||
htmledUrl = QString("<a href=\"%1\">%1</a>").arg(url);
|
|
||||||
out.replace(offset, exp.cap().length(), htmledUrl);
|
|
||||||
offset += htmledUrl.length();
|
|
||||||
}
|
|
||||||
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString ChatMessage::detectQuotes(const QString& str, MessageType type)
|
QString ChatMessage::detectQuotes(const QString& str, MessageType type)
|
||||||
{
|
{
|
||||||
// detect text quotes
|
// detect text quotes
|
||||||
|
|
|
@ -65,7 +65,6 @@ public:
|
||||||
void hideDate();
|
void hideDate();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
static QString detectAnchors(const QString& str);
|
|
||||||
static QString detectQuotes(const QString& str, MessageType type);
|
static QString detectQuotes(const QString& str, MessageType type);
|
||||||
static QString wrapDiv(const QString& str, const QString& div);
|
static QString wrapDiv(const QString& str, const QString& div);
|
||||||
|
|
||||||
|
|
|
@ -24,21 +24,24 @@
|
||||||
#include <QRegularExpression>
|
#include <QRegularExpression>
|
||||||
#include <QVector>
|
#include <QVector>
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
enum TextStyle
|
enum TextStyle
|
||||||
{
|
{
|
||||||
BOLD = 0,
|
BOLD = 0,
|
||||||
ITALIC,
|
ITALIC,
|
||||||
UNDERLINE,
|
UNDERLINE,
|
||||||
STRIKE,
|
STRIKE,
|
||||||
CODE
|
CODE,
|
||||||
|
HREF
|
||||||
};
|
};
|
||||||
|
|
||||||
static const QString COMMON_PATTERN = QStringLiteral("(?<=^|[^%1<])"
|
static const QString COMMON_PATTERN = QStringLiteral("(?<=^|[^%1<])"
|
||||||
"[%1]{%3}"
|
"[%1]{%2}"
|
||||||
"(?![%1 \\n])"
|
"(?![%1 \\n])"
|
||||||
".+?"
|
".+?"
|
||||||
"(?<![%1< \\n])"
|
"(?<![%1< \\n])"
|
||||||
"[%1]{%3}"
|
"[%1]{%2}"
|
||||||
"(?=$|[^%1])");
|
"(?=$|[^%1])");
|
||||||
|
|
||||||
static const QString MULTILINE_CODE = QStringLiteral("(?<=^|[^`])"
|
static const QString MULTILINE_CODE = QStringLiteral("(?<=^|[^`])"
|
||||||
|
@ -50,28 +53,43 @@ static const QString MULTILINE_CODE = QStringLiteral("(?<=^|[^`])"
|
||||||
"(?=$|[^`])");
|
"(?=$|[^`])");
|
||||||
|
|
||||||
// Items in vector associated with TextStyle values respectively. Do NOT change this order
|
// Items in vector associated with TextStyle values respectively. Do NOT change this order
|
||||||
static const QVector<QString> fontStylePatterns{QStringLiteral("<b>%1</b>"),
|
static const QVector<QString> htmlPatterns{QStringLiteral("<b>%1</b>"),
|
||||||
QStringLiteral("<i>%1</i>"),
|
QStringLiteral("<i>%1</i>"),
|
||||||
QStringLiteral("<u>%1</u>"),
|
QStringLiteral("<u>%1</u>"),
|
||||||
QStringLiteral("<s>%1</s>"),
|
QStringLiteral("<s>%1</s>"),
|
||||||
QStringLiteral(
|
QStringLiteral(
|
||||||
"<font color=#595959><code>%1</code></font>")};
|
"<font color=#595959><code>%1</code></font>"),
|
||||||
|
QStringLiteral("<a href=\"%1%2\">%2</a>")};
|
||||||
|
|
||||||
// Unfortunately, can't use simple QMap because ordered applying of styles is required
|
|
||||||
static const QVector<QPair<QRegularExpression, QString>> textPatternStyle{
|
static const QVector<QPair<QRegularExpression, QString>> textPatternStyle{
|
||||||
{QRegularExpression(COMMON_PATTERN.arg("*", "1")), fontStylePatterns[BOLD]},
|
{QRegularExpression(COMMON_PATTERN.arg("*", "1")), htmlPatterns[BOLD]},
|
||||||
{QRegularExpression(COMMON_PATTERN.arg("/", "1")), fontStylePatterns[ITALIC]},
|
{QRegularExpression(COMMON_PATTERN.arg("/", "1")), htmlPatterns[ITALIC]},
|
||||||
{QRegularExpression(COMMON_PATTERN.arg("_", "1")), fontStylePatterns[UNDERLINE]},
|
{QRegularExpression(COMMON_PATTERN.arg("_", "1")), htmlPatterns[UNDERLINE]},
|
||||||
{QRegularExpression(COMMON_PATTERN.arg("~", "1")), fontStylePatterns[STRIKE]},
|
{QRegularExpression(COMMON_PATTERN.arg("~", "1")), htmlPatterns[STRIKE]},
|
||||||
{QRegularExpression(COMMON_PATTERN.arg("`", "1")), fontStylePatterns[CODE]},
|
{QRegularExpression(COMMON_PATTERN.arg("`", "1")), htmlPatterns[CODE]},
|
||||||
{QRegularExpression(COMMON_PATTERN.arg("*", "2")), fontStylePatterns[BOLD]},
|
{QRegularExpression(COMMON_PATTERN.arg("*", "2")), htmlPatterns[BOLD]},
|
||||||
{QRegularExpression(COMMON_PATTERN.arg("/", "2")), fontStylePatterns[ITALIC]},
|
{QRegularExpression(COMMON_PATTERN.arg("/", "2")), htmlPatterns[ITALIC]},
|
||||||
{QRegularExpression(COMMON_PATTERN.arg("_", "2")), fontStylePatterns[UNDERLINE]},
|
{QRegularExpression(COMMON_PATTERN.arg("_", "2")), htmlPatterns[UNDERLINE]},
|
||||||
{QRegularExpression(COMMON_PATTERN.arg("~", "2")), fontStylePatterns[STRIKE]},
|
{QRegularExpression(COMMON_PATTERN.arg("~", "2")), htmlPatterns[STRIKE]},
|
||||||
{QRegularExpression(MULTILINE_CODE), fontStylePatterns[CODE]}};
|
{QRegularExpression(MULTILINE_CODE), htmlPatterns[CODE]}};
|
||||||
|
|
||||||
|
static const QVector<QRegularExpression> urlPatterns {
|
||||||
|
QRegularExpression("((\\bhttp[s]?://(www\\.)?)|(\\bwww\\.))"
|
||||||
|
"[^. \\n]+\\.[^ \\n]+"),
|
||||||
|
QRegularExpression("\\b(ftp|smb)://[^ \\n]+"),
|
||||||
|
QRegularExpression("\\bfile://(localhost)?/[^ \\n]+"),
|
||||||
|
QRegularExpression("\\btox:[a-zA-Z\\d]{76}"),
|
||||||
|
QRegularExpression("\\b(mailto|tox):[^ \\n]+@[^ \\n]+")
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class TextFormatter
|
||||||
|
*
|
||||||
|
* @brief This class applies formatting to the text messages, e.g. font styling and URL highlighting
|
||||||
|
*/
|
||||||
|
|
||||||
TextFormatter::TextFormatter(const QString& str)
|
TextFormatter::TextFormatter(const QString& str)
|
||||||
: sourceString(str)
|
: message(str)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,18 +128,37 @@ static bool isTagIntersection(const QString& str)
|
||||||
return openingTagCount != closingTagCount;
|
return openingTagCount != closingTagCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Applies a function for URL's which can be extracted from passed string
|
||||||
|
* @param str String in which we are looking for URL's
|
||||||
|
* @param func Function which is applying to URL
|
||||||
|
*/
|
||||||
|
static void processUrl(QString& str, std::function<QString(QString&)> func)
|
||||||
|
{
|
||||||
|
for (QRegularExpression exp : urlPatterns) {
|
||||||
|
QRegularExpressionMatchIterator iter = exp.globalMatch(str);
|
||||||
|
while (iter.hasNext()) {
|
||||||
|
QRegularExpressionMatch match = iter.next();
|
||||||
|
int startPos = match.capturedStart();
|
||||||
|
int length = match.capturedLength();
|
||||||
|
QString url = str.mid(startPos, length);
|
||||||
|
str.replace(startPos, length, func(url));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Applies styles to the font of text that was passed to the constructor
|
* @brief Applies styles to the font of text that was passed to the constructor
|
||||||
* @param showFormattingSymbols True, if it is supposed to include formatting symbols into resulting
|
* @param showFormattingSymbols True, if it is supposed to include formatting symbols into resulting
|
||||||
* string
|
* string
|
||||||
* @return Source text with styled font
|
|
||||||
*/
|
*/
|
||||||
QString TextFormatter::applyHtmlFontStyling(bool showFormattingSymbols)
|
void TextFormatter::applyHtmlFontStyling(bool showFormattingSymbols)
|
||||||
{
|
{
|
||||||
QString out = sourceString;
|
processUrl(message, [] (QString& str) {
|
||||||
|
return str.replace("/", "/");
|
||||||
|
});
|
||||||
for (QPair<QRegularExpression, QString> pair : textPatternStyle) {
|
for (QPair<QRegularExpression, QString> pair : textPatternStyle) {
|
||||||
QRegularExpressionMatchIterator matchesIterator = pair.first.globalMatch(out);
|
QRegularExpressionMatchIterator matchesIterator = pair.first.globalMatch(message);
|
||||||
int insertedTagSymbolsCount = 0;
|
int insertedTagSymbolsCount = 0;
|
||||||
|
|
||||||
while (matchesIterator.hasNext()) {
|
while (matchesIterator.hasNext()) {
|
||||||
|
@ -133,19 +170,29 @@ QString TextFormatter::applyHtmlFontStyling(bool showFormattingSymbols)
|
||||||
int capturedStart = match.capturedStart() + insertedTagSymbolsCount;
|
int capturedStart = match.capturedStart() + insertedTagSymbolsCount;
|
||||||
int capturedLength = match.capturedLength();
|
int capturedLength = match.capturedLength();
|
||||||
|
|
||||||
QString stylingText = out.mid(capturedStart, capturedLength);
|
QString stylingText = message.mid(capturedStart, capturedLength);
|
||||||
int choppingSignsCount = showFormattingSymbols ? 0 : patternSignsCount(stylingText);
|
int choppingSignsCount = showFormattingSymbols ? 0 : patternSignsCount(stylingText);
|
||||||
int textStart = capturedStart + choppingSignsCount;
|
int textStart = capturedStart + choppingSignsCount;
|
||||||
int textLength = capturedLength - 2 * choppingSignsCount;
|
int textLength = capturedLength - 2 * choppingSignsCount;
|
||||||
|
|
||||||
QString styledText = pair.second.arg(out.mid(textStart, textLength));
|
QString styledText = pair.second.arg(message.mid(textStart, textLength));
|
||||||
|
|
||||||
out.replace(capturedStart, capturedLength, styledText);
|
message.replace(capturedStart, capturedLength, styledText);
|
||||||
// Subtracting length of "%1"
|
// Subtracting length of "%1"
|
||||||
insertedTagSymbolsCount += pair.second.length() - 2 - 2 * choppingSignsCount;
|
insertedTagSymbolsCount += pair.second.length() - 2 - 2 * choppingSignsCount;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return out;
|
message.replace("/", "/");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Wraps all found URL's in HTML hyperlink tag
|
||||||
|
*/
|
||||||
|
void TextFormatter::wrapUrl()
|
||||||
|
{
|
||||||
|
processUrl(message, [] (QString& str) {
|
||||||
|
return htmlPatterns[TextStyle::HREF].arg(str.startsWith("www") ? "http://" : "", str);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -156,5 +203,8 @@ QString TextFormatter::applyHtmlFontStyling(bool showFormattingSymbols)
|
||||||
*/
|
*/
|
||||||
QString TextFormatter::applyStyling(bool showFormattingSymbols)
|
QString TextFormatter::applyStyling(bool showFormattingSymbols)
|
||||||
{
|
{
|
||||||
return applyHtmlFontStyling(showFormattingSymbols);
|
message.toHtmlEscaped();
|
||||||
|
applyHtmlFontStyling(showFormattingSymbols);
|
||||||
|
wrapUrl();
|
||||||
|
return message;
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,9 +25,11 @@
|
||||||
class TextFormatter
|
class TextFormatter
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
QString sourceString;
|
QString message;
|
||||||
|
|
||||||
QString applyHtmlFontStyling(bool showFormattingSymbols);
|
void wrapUrl();
|
||||||
|
|
||||||
|
void applyHtmlFontStyling(bool showFormattingSymbols);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit TextFormatter(const QString& str);
|
explicit TextFormatter(const QString& str);
|
||||||
|
|
Loading…
Reference in New Issue
Block a user