Add support for Underline and Strike-through text.

This commit is contained in:
John Preston 2019-06-23 15:40:59 +02:00
parent d864ebd695
commit 8741266819
13 changed files with 166 additions and 39 deletions

View File

@ -1536,6 +1536,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_menu_formatting" = "Formatting";
"lng_menu_formatting_bold" = "Bold";
"lng_menu_formatting_italic" = "Italic";
"lng_menu_formatting_underline" = "Underline";
"lng_menu_formatting_strike_out" = "Strike-through";
"lng_menu_formatting_monospace" = "Monospace";
"lng_menu_formatting_link_create" = "Create link";
"lng_menu_formatting_link_edit" = "Edit link";

View File

@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/widgets/buttons.h"
#include "ui/widgets/input_fields.h"
#include "ui/widgets/labels.h"
#include "ui/text/text_utilities.h"
#include "core/click_handler_types.h" // UrlClickHandler
#include "base/qthelp_url.h" // qthelp::url_encode
#include "platform/platform_info.h" // Platform::SystemVersionPretty
@ -268,15 +269,13 @@ void ConfirmPhoneBox::launch() {
}
void ConfirmPhoneBox::prepare() {
_about.create(this, st::confirmPhoneAboutLabel);
TextWithEntities aboutText;
auto formattedPhone = App::formatPhone(_phone);
aboutText.text = tr::lng_confirm_phone_about(tr::now, lt_phone, formattedPhone);
auto phonePosition = aboutText.text.indexOf(formattedPhone);
if (phonePosition >= 0) {
aboutText.entities.push_back({ EntityType::Bold, phonePosition, formattedPhone.size() });
}
_about->setMarkedText(aboutText);
_about.create(
this,
tr::lng_confirm_phone_about(
lt_phone,
rpl::single(Ui::Text::Bold(App::formatPhone(_phone))),
Ui::Text::WithEntities),
st::confirmPhoneAboutLabel);
_code.create(this, st::confirmPhoneCodeField, tr::lng_code_ph());
_code->setAutoSubmit(_sentCodeLength, [=] { sendCode(); });

View File

@ -237,6 +237,10 @@ EntitiesInText ConvertTextTagsToEntities(const TextWithTags::Tags &tags) {
push(EntityType::Bold);
} else if (tag.id == Ui::InputField::kTagItalic) {
push(EntityType::Italic);
} else if (tag.id == Ui::InputField::kTagUnderline) {
push(EntityType::Underline);
} else if (tag.id == Ui::InputField::kTagStrikeOut) {
push(EntityType::StrikeOut);
} else if (tag.id == Ui::InputField::kTagCode) {
push(EntityType::Code);
} else if (tag.id == Ui::InputField::kTagPre) { // #TODO entities
@ -274,8 +278,14 @@ TextWithTags::Tags ConvertEntitiesToTextTags(const EntitiesInText &entities) {
}
} break;
case EntityType::Bold: push(Ui::InputField::kTagBold); break;
case EntityType::Italic: push(Ui::InputField::kTagItalic); break; // #TODO entities
case EntityType::Code: push(Ui::InputField::kTagCode); break;
case EntityType::Italic: push(Ui::InputField::kTagItalic); break;
case EntityType::Underline:
push(Ui::InputField::kTagUnderline);
break;
case EntityType::StrikeOut:
push(Ui::InputField::kTagStrikeOut);
break;
case EntityType::Code: push(Ui::InputField::kTagCode); break; // #TODO entities
case EntityType::Pre: push(Ui::InputField::kTagPre); break;
}
}

View File

@ -64,6 +64,7 @@ FontData::FontData(int size, uint32 flags, int family, Font *other)
}
f.setItalic(_flags & FontItalic);
f.setUnderline(_flags & FontUnderline);
f.setStrikeOut(_flags & FontStrikeOut);
f.setStyleStrategy(QFont::PreferQuality);
m = QFontMetrics(f);
@ -86,6 +87,10 @@ Font FontData::underline(bool set) const {
return otherFlagsFont(FontUnderline, set);
}
Font FontData::strikeout(bool set) const {
return otherFlagsFont(FontStrikeOut, set);
}
int FontData::size() const {
return _size;
}

View File

@ -56,8 +56,9 @@ enum FontFlags {
FontBold = 0x01,
FontItalic = 0x02,
FontUnderline = 0x04,
FontStrikeOut = 0x08,
FontDifferentFlags = 0x08,
FontDifferentFlags = 0x10,
};
class FontData {
@ -79,6 +80,7 @@ public:
Font bold(bool set = true) const;
Font italic(bool set = true) const;
Font underline(bool set = true) const;
Font strikeout(bool set = true) const;
int size() const;
uint32 flags() const;

View File

@ -84,7 +84,12 @@ TextWithEntities PrepareRichFromRich(
(type == EntityType::Hashtag && !parseHashtags) ||
(type == EntityType::Cashtag && !parseHashtags) ||
(type == EntityType::BotCommand && !parseBotCommands) || // #TODO entities
((type == EntityType::Bold || type == EntityType::Italic || type == EntityType::Code || type == EntityType::Pre) && !parseMarkdown)) {
(!parseMarkdown && (type == EntityType::Bold
|| type == EntityType::Italic
|| type == EntityType::Underline
|| type == EntityType::StrikeOut
|| type == EntityType::Code
|| type == EntityType::Pre))) {
continue;
}
result.entities.push_back(preparsed.at(i));
@ -545,6 +550,10 @@ bool Parser::checkEntities() {
flags = TextBlockFSemibold;
} else if (entityType == EntityType::Italic) {
flags = TextBlockFItalic;
} else if (entityType == EntityType::Underline) {
flags = TextBlockFUnderline;
} else if (entityType == EntityType::StrikeOut) {
flags = TextBlockFStrikeOut;
} else if (entityType == EntityType::Code) { // #TODO entities
flags = TextBlockFCode;
} else if (entityType == EntityType::Pre) {
@ -699,6 +708,20 @@ bool Parser::readCommand() {
}
break;
case TextCommandStrikeOut:
if (!(_flags & TextBlockFStrikeOut)) {
createBlock();
_flags |= TextBlockFStrikeOut;
}
break;
case TextCommandNoStrikeOut:
if (_flags & TextBlockFStrikeOut) {
createBlock();
_flags &= ~TextBlockFStrikeOut;
}
break;
case TextCommandLinkIndex:
if (_ptr->unicode() != _lnkIndex) {
createBlock();
@ -2035,6 +2058,7 @@ private:
}
if (flags & TextBlockFItalic) result = result->italic();
if (flags & TextBlockFUnderline) result = result->underline();
if (flags & TextBlockFStrikeOut) result = result->strikeout();
if (flags & TextBlockFTilde) { // tilde fix in OpenSans
result = st::semiboldFont;
}
@ -3217,6 +3241,8 @@ TextForMimeData String::toText(
? std::vector<MarkdownTagTracker>{
{ TextBlockFItalic, EntityType::Italic },
{ TextBlockFSemibold, EntityType::Bold },
{ TextBlockFUnderline, EntityType::Underline },
{ TextBlockFStrikeOut, EntityType::StrikeOut },
{ TextBlockFCode, EntityType::Code }, // #TODO entities
{ TextBlockFPre, EntityType::Pre }
} : std::vector<MarkdownTagTracker>();

View File

@ -21,10 +21,12 @@ enum TextCommands {
TextCommandNoItalic = 0x04,
TextCommandUnderline = 0x05,
TextCommandNoUnderline = 0x06,
TextCommandSemibold = 0x07,
TextCommandNoSemibold = 0x08,
TextCommandLinkIndex = 0x09, // 0 - NoLink
TextCommandLinkText = 0x0A,
TextCommandStrikeOut = 0x07,
TextCommandNoStrikeOut = 0x08,
TextCommandSemibold = 0x09,
TextCommandNoSemibold = 0x0A,
TextCommandLinkIndex = 0x0B, // 0 - NoLink
TextCommandLinkText = 0x0C,
TextCommandSkipBlock = 0x0D,
TextCommandLangTag = 0x20,

View File

@ -322,6 +322,7 @@ TextBlock::TextBlock(const style::font &font, const QString &str, QFixed minResi
}
if (flags & TextBlockFItalic) blockFont = blockFont->italic();
if (flags & TextBlockFUnderline) blockFont = blockFont->underline();
if (flags & TextBlockFStrikeOut) blockFont = blockFont->strikeout();
if (flags & TextBlockFTilde) { // tilde fix in OpenSans
blockFont = st::semiboldFont;
}

View File

@ -23,10 +23,11 @@ enum TextBlockFlags {
TextBlockFBold = 0x01,
TextBlockFItalic = 0x02,
TextBlockFUnderline = 0x04,
TextBlockFTilde = 0x08, // tilde fix in OpenSans
TextBlockFSemibold = 0x10,
TextBlockFCode = 0x20,
TextBlockFPre = 0x40,
TextBlockFStrikeOut = 0x08,
TextBlockFTilde = 0x10, // tilde fix in OpenSans
TextBlockFSemibold = 0x20,
TextBlockFCode = 0x40,
TextBlockFPre = 0x80,
};
class AbstractBlock {

View File

@ -36,7 +36,7 @@ QString ExpressionSeparators(const QString &additional) {
QString Separators(const QString &additional) {
static const auto quotes = Quotes();
return qsl(" \x10\n\r\t.,:;<>|'\"[]{}~!?%^()-+=")
return qsl(" \x10\n\r\t.,:;<>|'\"[]{}!?%^()-+=")
+ QChar(0xfdd0) // QTextBeginningOfFrame
+ QChar(0xfdd1) // QTextEndOfFrame
+ QChar(QChar::ParagraphSeparator)
@ -46,15 +46,19 @@ QString Separators(const QString &additional) {
}
QString SeparatorsBold() {
return Separators(qsl("`/"));
return Separators(qsl("`~/"));
}
QString SeparatorsItalic() {
return Separators(qsl("`*/"));
return Separators(qsl("`*~/"));
}
QString SeparatorsStrikeOut() {
return Separators(qsl("`*~/"));
}
QString SeparatorsMono() {
return Separators(qsl("*/"));
return Separators(qsl("*~/"));
}
QString ExpressionHashtag() {
@ -1173,6 +1177,14 @@ QString MarkdownItalicBadAfter() {
return qsl("_");
}
QString MarkdownStrikeOutGoodBefore() {
return SeparatorsStrikeOut();
}
QString MarkdownStrikeOutBadAfter() {
return qsl("~");
}
QString MarkdownCodeGoodBefore() {
return SeparatorsMono();
}
@ -1500,6 +1512,8 @@ EntitiesInText EntitiesFromMTP(const QVector<MTPMessageEntity> &entities) {
case mtpc_messageEntityBotCommand: { auto &d = entity.c_messageEntityBotCommand(); result.push_back({ EntityType::BotCommand, d.voffset.v, d.vlength.v }); } break;
case mtpc_messageEntityBold: { auto &d = entity.c_messageEntityBold(); result.push_back({ EntityType::Bold, d.voffset.v, d.vlength.v }); } break;
case mtpc_messageEntityItalic: { auto &d = entity.c_messageEntityItalic(); result.push_back({ EntityType::Italic, d.voffset.v, d.vlength.v }); } break;
case mtpc_messageEntityUnderline: { auto &d = entity.c_messageEntityUnderline(); result.push_back({ EntityType::Underline, d.voffset.v, d.vlength.v }); } break;
case mtpc_messageEntityStrike: { auto &d = entity.c_messageEntityStrike(); result.push_back({ EntityType::StrikeOut, d.voffset.v, d.vlength.v }); } break;
case mtpc_messageEntityCode: { auto &d = entity.c_messageEntityCode(); result.push_back({ EntityType::Code, d.voffset.v, d.vlength.v }); } break;
case mtpc_messageEntityPre: { auto &d = entity.c_messageEntityPre(); result.push_back({ EntityType::Pre, d.voffset.v, d.vlength.v, Clean(qs(d.vlanguage)) }); } break;
// #TODO entities
@ -1517,6 +1531,8 @@ MTPVector<MTPMessageEntity> EntitiesToMTP(const EntitiesInText &entities, Conver
if (option == ConvertOption::SkipLocal
&& entity.type() != EntityType::Bold
&& entity.type() != EntityType::Italic
&& entity.type() != EntityType::Underline
&& entity.type() != EntityType::StrikeOut
&& entity.type() != EntityType::Code // #TODO entities
&& entity.type() != EntityType::Pre
&& entity.type() != EntityType::MentionName
@ -1550,6 +1566,8 @@ MTPVector<MTPMessageEntity> EntitiesToMTP(const EntitiesInText &entities, Conver
case EntityType::BotCommand: v.push_back(MTP_messageEntityBotCommand(offset, length)); break;
case EntityType::Bold: v.push_back(MTP_messageEntityBold(offset, length)); break;
case EntityType::Italic: v.push_back(MTP_messageEntityItalic(offset, length)); break;
case EntityType::Underline: v.push_back(MTP_messageEntityUnderline(offset, length)); break;
case EntityType::StrikeOut: v.push_back(MTP_messageEntityStrike(offset, length)); break;
case EntityType::Code: v.push_back(MTP_messageEntityCode(offset, length)); break; // #TODO entities
case EntityType::Pre: v.push_back(MTP_messageEntityPre(offset, length, MTP_string(entity.data()))); break;
}

View File

@ -21,6 +21,8 @@ enum class EntityType {
Bold,
Italic,
Underline,
StrikeOut,
Code, // inline
Pre, // block
};
@ -267,6 +269,8 @@ QString MarkdownBoldGoodBefore();
QString MarkdownBoldBadAfter();
QString MarkdownItalicGoodBefore();
QString MarkdownItalicBadAfter();
QString MarkdownStrikeOutGoodBefore();
QString MarkdownStrikeOutBadAfter();
QString MarkdownCodeGoodBefore();
QString MarkdownCodeBadAfter();
QString MarkdownPreGoodBefore();

View File

@ -38,6 +38,8 @@ const auto kObjectReplacement = QString::fromRawData(
1);
const auto &kTagBold = InputField::kTagBold;
const auto &kTagItalic = InputField::kTagItalic;
const auto &kTagUnderline = InputField::kTagUnderline;
const auto &kTagStrikeOut = InputField::kTagStrikeOut;
const auto &kTagCode = InputField::kTagCode;
const auto &kTagPre = InputField::kTagPre;
const auto kNewlineChars = QString("\r\n")
@ -46,6 +48,7 @@ const auto kNewlineChars = QString("\r\n")
+ QChar(QChar::ParagraphSeparator)
+ QChar(QChar::LineSeparator);
const auto kClearFormatSequence = QKeySequence("ctrl+shift+n");
const auto kStrikeOutSequence = QKeySequence("ctrl+shift+x");
const auto kMonospaceSequence = QKeySequence("ctrl+shift+m");
const auto kEditLinkSequence = QKeySequence("ctrl+k");
@ -175,12 +178,16 @@ struct TagStartExpression {
QString tag;
QString goodBefore;
QString badAfter;
QString badBefore;
QString goodAfter;
};
constexpr auto kTagBoldIndex = 0;
constexpr auto kTagItalicIndex = 1;
constexpr auto kTagCodeIndex = 2;
constexpr auto kTagPreIndex = 3;
//constexpr auto kTagUnderlineIndex = 2;
constexpr auto kTagStrikeOutIndex = 2;
constexpr auto kTagCodeIndex = 3;
constexpr auto kTagPreIndex = 4;
constexpr auto kInvalidPosition = std::numeric_limits<int>::max() / 2;
class TagSearchItem {
@ -208,24 +215,34 @@ public:
const auto length = text.size();
const auto &tag = expression.tag;
const auto tagLength = tag.size();
const auto isGood = [&](QChar ch) {
return (expression.goodBefore.indexOf(ch) >= 0);
const auto isGoodBefore = [&](QChar ch) {
return expression.goodBefore.isEmpty()
|| (expression.goodBefore.indexOf(ch) >= 0);
};
const auto isBad = [&](QChar ch) {
return (expression.badAfter.indexOf(ch) >= 0);
const auto isBadAfter = [&](QChar ch) {
return !expression.badAfter.isEmpty()
&& (expression.badAfter.indexOf(ch) >= 0);
};
const auto isBadBefore = [&](QChar ch) {
return !expression.badBefore.isEmpty()
&& (expression.badBefore.indexOf(ch) >= 0);
};
const auto isGoodAfter = [&](QChar ch) {
return expression.goodAfter.isEmpty()
|| (expression.goodAfter.indexOf(ch) >= 0);
};
const auto check = [&](Edge edge) {
if (_position > 0) {
const auto before = text[_position - 1];
if ((edge == Edge::Open && !isGood(before))
|| (edge == Edge::Close && isBad(before))) {
if ((edge == Edge::Open && !isGoodBefore(before))
|| (edge == Edge::Close && isBadBefore(before))) {
return false;
}
}
if (_position + tagLength < length) {
const auto after = text[_position + tagLength];
if ((edge == Edge::Open && isBad(after))
|| (edge == Edge::Close && !isGood(after))) {
if ((edge == Edge::Open && isBadAfter(after))
|| (edge == Edge::Close && !isGoodAfter(after))) {
return false;
}
}
@ -275,22 +292,44 @@ const std::vector<TagStartExpression> &TagStartExpressions() {
{
kTagBold,
TextUtilities::MarkdownBoldGoodBefore(),
TextUtilities::MarkdownBoldBadAfter()
TextUtilities::MarkdownBoldBadAfter(),
TextUtilities::MarkdownBoldBadAfter(),
TextUtilities::MarkdownBoldGoodBefore()
},
{
kTagItalic,
TextUtilities::MarkdownItalicGoodBefore(),
TextUtilities::MarkdownItalicBadAfter()
TextUtilities::MarkdownItalicBadAfter(),
TextUtilities::MarkdownItalicBadAfter(),
TextUtilities::MarkdownItalicGoodBefore()
},
//{
// kTagUnderline,
// TextUtilities::MarkdownUnderlineGoodBefore(),
// TextUtilities::MarkdownUnderlineBadAfter(),
// TextUtilities::MarkdownUnderlineBadAfter(),
// TextUtilities::MarkdownUnderlineGoodBefore()
//},
{
kTagStrikeOut,
TextUtilities::MarkdownStrikeOutGoodBefore(),
TextUtilities::MarkdownStrikeOutBadAfter(),
TextUtilities::MarkdownStrikeOutBadAfter(),
QString(),
},
{
kTagCode,
TextUtilities::MarkdownCodeGoodBefore(),
TextUtilities::MarkdownCodeBadAfter()
TextUtilities::MarkdownCodeBadAfter(),
TextUtilities::MarkdownCodeBadAfter(),
TextUtilities::MarkdownCodeGoodBefore()
},
{
kTagPre,
TextUtilities::MarkdownPreGoodBefore(),
TextUtilities::MarkdownPreBadAfter()
TextUtilities::MarkdownPreBadAfter(),
TextUtilities::MarkdownPreBadAfter(),
TextUtilities::MarkdownPreGoodBefore()
},
};
return cached;
@ -300,6 +339,8 @@ const std::map<QString, int> &TagIndices() {
static auto cached = std::map<QString, int> {
{ kTagBold, kTagBoldIndex },
{ kTagItalic, kTagItalicIndex },
//{ kTagUnderline, kTagUnderlineIndex },
{ kTagStrikeOut, kTagStrikeOutIndex },
{ kTagCode, kTagCodeIndex },
{ kTagPre, kTagPreIndex },
};
@ -646,6 +687,12 @@ QTextCharFormat PrepareTagFormat(
} else if (tag == kTagItalic) {
result.setForeground(st.textFg);
result.setFont(st.font->italic());
} else if (tag == kTagUnderline) {
result.setForeground(st.textFg);
result.setFont(st.font->underline());
} else if (tag == kTagStrikeOut) {
result.setForeground(st.textFg);
result.setFont(st.font->strikeout());
} else if (tag == kTagCode || tag == kTagPre) {
result.setForeground(st::defaultTextPalette.monoFg);
result.setFont(AdjustFont(App::monofont(), st.font));
@ -794,6 +841,8 @@ QString ExpandCustomLinks(const TextWithTags &text) {
const QString InputField::kTagBold = qsl("**");
const QString InputField::kTagItalic = qsl("__");
const QString InputField::kTagUnderline = qsl("^^"); // Not for Markdown.
const QString InputField::kTagStrikeOut = qsl("~~");
const QString InputField::kTagCode = qsl("`");
const QString InputField::kTagPre = qsl("```");
@ -2694,6 +2743,10 @@ bool InputField::handleMarkdownKey(QKeyEvent *e) {
toggleSelectionMarkdown(kTagBold);
} else if (e == QKeySequence::Italic) {
toggleSelectionMarkdown(kTagItalic);
} else if (e == QKeySequence::Underline) {
toggleSelectionMarkdown(kTagUnderline);
} else if (matches(kStrikeOutSequence)) {
toggleSelectionMarkdown(kTagStrikeOut);
} else if (matches(kMonospaceSequence)) {
toggleSelectionMarkdown(kTagCode);
} else if (matches(kClearFormatSequence)) {
@ -3358,6 +3411,8 @@ void InputField::addMarkdownActions(
addtag(tr::lng_menu_formatting_bold(tr::now), QKeySequence::Bold, kTagBold);
addtag(tr::lng_menu_formatting_italic(tr::now), QKeySequence::Italic, kTagItalic);
addtag(tr::lng_menu_formatting_underline(tr::now), QKeySequence::Underline, kTagUnderline);
addtag(tr::lng_menu_formatting_strike_out(tr::now), kStrikeOutSequence, kTagStrikeOut);
addtag(tr::lng_menu_formatting_monospace(tr::now), kMonospaceSequence, kTagCode);
if (_editLinkCallback) {

View File

@ -151,6 +151,8 @@ public:
};
static const QString kTagBold;
static const QString kTagItalic;
static const QString kTagUnderline;
static const QString kTagStrikeOut;
static const QString kTagCode;
static const QString kTagPre;