This commit is contained in:
John Preston 2016-04-14 15:03:06 +03:00
commit 971ec71836
23 changed files with 2791 additions and 2579 deletions

View file

@ -957,7 +957,6 @@ void ContactsInner::peopleReceived(const QString &query, const QVector<MTPPeer>
if (p->asUser()->botInfo->cantJoinGroups) continue;
}
if (_channel) {
if (_channel->isMegagroup() && _membersFilter == MembersFilterAdmins) continue;
if (!_channel->isMegagroup() && _membersFilter != MembersFilterAdmins) continue;
}
}

View file

@ -20,7 +20,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
*/
#pragma once
#include "ui/text.h"
#include "ui/text/text.h"
class History;
class HistoryItem;

View file

@ -2626,10 +2626,10 @@ void ReplyKeyboard::resize(int width, int height) {
}
bool ReplyKeyboard::isEnoughSpace(int width, const style::botKeyboardButton &st) const {
for_const (const ButtonRow &row, _rows) {
for_const (const auto &row, _rows) {
int s = row.size();
int widthLeft = width - ((s - 1) * st.margin + s * 2 * st.padding);
for_const (const Button &button, row) {
for_const (const auto &button, row) {
widthLeft -= qMax(button.text.maxWidth(), 1);
if (widthLeft < 0) {
if (row.size() > 3) {
@ -2648,18 +2648,15 @@ void ReplyKeyboard::setStyle(StylePtr &&st) {
}
int ReplyKeyboard::naturalWidth() const {
int result = 0;
auto result = 0;
for_const (const auto &row, _rows) {
auto rowMaxButtonWidth = 0;
for_const (const auto &button, row) {
accumulate_max(rowMaxButtonWidth, qMax(button.text.maxWidth(), 1) + _st->minButtonWidth(button.type));
}
auto markup = _item->Get<HistoryMessageReplyMarkup>();
for_const (const ButtonRow &row, _rows) {
int rowSize = row.size();
int rowWidth = (rowSize - 1) * _st->buttonSkip();
for_const (const Button &button, row) {
rowWidth += qMax(button.text.maxWidth(), 1) + _st->minButtonWidth(button.type);
}
if (rowWidth > result) {
result = rowWidth;
}
auto rowSize = row.size();
accumulate_max(result, rowSize * rowMaxButtonWidth + (rowSize - 1) * _st->buttonSkip());
}
return result;
}
@ -2944,6 +2941,24 @@ void HistoryMediaPtr::reset(HistoryMedia *p) {
}
}
namespace internal {
TextSelection unshiftSelection(TextSelection selection, const Text &byText) {
if (selection == FullSelection) {
return selection;
}
return ::unshiftSelection(selection, byText);
}
TextSelection shiftSelection(TextSelection selection, const Text &byText) {
if (selection == FullSelection) {
return selection;
}
return ::shiftSelection(selection, byText);
}
} // namespace internal
HistoryItem::HistoryItem(History *history, MsgId msgId, MTPDmessage::Flags flags, QDateTime msgDate, int32 from) : HistoryElem()
, y(0)
, id(msgId)
@ -3242,6 +3257,7 @@ void RadialAnimation::draw(Painter &p, const QRect &inner, int32 thickness, cons
}
namespace {
int32 documentMaxStatusWidth(DocumentData *document) {
int32 result = st::normalFont->width(formatDownloadText(document->size, document->size));
if (SongData *song = document->song()) {
@ -3263,7 +3279,20 @@ namespace {
result = qMax(result, st::normalFont->width(formatGifAndSizeText(document->size)));
return result;
}
QString captionedSelectedText(const QString &attachType, const Text &caption, TextSelection selection) {
if (selection != FullSelection) {
return caption.original(selection, Text::ExpandLinksAll);
}
QString result;
result.reserve(5 + attachType.size() + caption.length());
result.append(qstr("[ ")).append(attachType).append(qstr(" ]"));
if (!caption.isEmpty()) {
result.append(qstr("\n")).append(caption.original(AllTextSelection));
}
return result;
}
} // namespace
void HistoryFileMedia::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) {
if (p == _savel || p == _cancell) {
@ -3681,12 +3710,12 @@ void HistoryPhoto::detachFromParent() {
App::unregPhotoItem(_data, _parent);
}
const QString HistoryPhoto::inDialogsText() const {
QString HistoryPhoto::inDialogsText() const {
return _caption.isEmpty() ? lang(lng_in_dlg_photo) : _caption.original(AllTextSelection, Text::ExpandLinksNone);
}
const QString HistoryPhoto::inHistoryText() const {
return qsl("[ ") + lang(lng_in_dlg_photo) + (_caption.isEmpty() ? QString() : (qsl(", ") + _caption.original(AllTextSelection, Text::ExpandLinksAll))) + qsl(" ]");
QString HistoryPhoto::selectedText(TextSelection selection) const {
return captionedSelectedText(lang(lng_in_dlg_photo), _caption, selection);
}
ImagePtr HistoryPhoto::replyPreview() {
@ -3902,7 +3931,7 @@ HistoryTextState HistoryVideo::getState(int x, int y, HistoryStateRequest reques
int32 captionw = width - st::msgPadding.left() - st::msgPadding.right();
height -= _caption.countHeight(captionw) + st::msgPadding.bottom();
if (x >= st::msgPadding.left() && y >= height && x < st::msgPadding.left() + captionw && y < _height) {
result = _caption.getState(x - st::msgPadding.left(), y - height, captionw);
result = _caption.getState(x - st::msgPadding.left(), y - height, captionw, request.forText());
}
height -= st::mediaCaptionSkip;
}
@ -3927,12 +3956,12 @@ void HistoryVideo::setStatusSize(int32 newSize) const {
HistoryFileMedia::setStatusSize(newSize, _data->size, _data->duration(), 0);
}
const QString HistoryVideo::inDialogsText() const {
QString HistoryVideo::inDialogsText() const {
return _caption.isEmpty() ? lang(lng_in_dlg_video) : _caption.original(AllTextSelection, Text::ExpandLinksNone);
}
const QString HistoryVideo::inHistoryText() const {
return qsl("[ ") + lang(lng_in_dlg_video) + (_caption.isEmpty() ? QString() : (qsl(", ") + _caption.original(AllTextSelection, Text::ExpandLinksAll))) + qsl(" ]");
QString HistoryVideo::selectedText(TextSelection selection) const {
return captionedSelectedText(lang(lng_in_dlg_video), _caption, selection);
}
void HistoryVideo::updateStatusText() const {
@ -4404,7 +4433,7 @@ HistoryTextState HistoryDocument::getState(int x, int y, HistoryStateRequest req
return result;
}
const QString HistoryDocument::inDialogsText() const {
QString HistoryDocument::inDialogsText() const {
QString result;
if (Has<HistoryDocumentVoice>()) {
result = lang(lng_in_dlg_audio);
@ -4425,26 +4454,24 @@ const QString HistoryDocument::inDialogsText() const {
return result;
}
const QString HistoryDocument::inHistoryText() const {
QString result;
QString HistoryDocument::selectedText(TextSelection selection) const {
const Text emptyCaption;
const Text *caption = &emptyCaption;
if (auto captioned = Get<HistoryDocumentCaptioned>()) {
caption = &captioned->_caption;
}
QString attachType = lang(lng_in_dlg_file);
if (Has<HistoryDocumentVoice>()) {
result = lang(lng_in_dlg_audio);
attachType = lang(lng_in_dlg_audio);
} else if (_data->song()) {
result = lang(lng_in_dlg_audio_file);
} else {
result = lang(lng_in_dlg_file);
attachType = lang(lng_in_dlg_audio_file);
}
if (auto named = Get<HistoryDocumentNamed>()) {
if (!named->_name.isEmpty()) {
result.append(qsl(" : ")).append(named->_name);
attachType.append(qstr(" : ")).append(named->_name);
}
}
if (auto captioned = Get<HistoryDocumentCaptioned>()) {
if (!captioned->_caption.isEmpty()) {
result.append(qsl(", ")).append(captioned->_caption.original(AllTextSelection, Text::ExpandLinksAll));
}
}
return qsl("[ ") + result.append(qsl(" ]"));
return captionedSelectedText(attachType, *caption, selection);
}
void HistoryDocument::setStatusSize(int32 newSize, qint64 realDuration) const {
@ -4878,12 +4905,12 @@ HistoryTextState HistoryGif::getState(int x, int y, HistoryStateRequest request)
return result;
}
const QString HistoryGif::inDialogsText() const {
QString HistoryGif::inDialogsText() const {
return qsl("GIF") + (_caption.isEmpty() ? QString() : (' ' + _caption.original(AllTextSelection, Text::ExpandLinksNone)));
}
const QString HistoryGif::inHistoryText() const {
return qsl("[ GIF ") + (_caption.isEmpty() ? QString() : (_caption.original(AllTextSelection, Text::ExpandLinksAll) + ' ')) + qsl(" ]");
QString HistoryGif::selectedText(TextSelection selection) const {
return captionedSelectedText(qsl("GIF"), _caption, selection);
}
void HistoryGif::setStatusSize(int32 newSize) const {
@ -5195,11 +5222,14 @@ HistoryTextState HistorySticker::getState(int x, int y, HistoryStateRequest requ
return result;
}
const QString HistorySticker::inDialogsText() const {
QString HistorySticker::inDialogsText() const {
return _emoji.isEmpty() ? lang(lng_in_dlg_sticker) : lng_in_dlg_sticker_emoji(lt_emoji, _emoji);
}
const QString HistorySticker::inHistoryText() const {
QString HistorySticker::selectedText(TextSelection selection) const {
if (selection != FullSelection) {
return QString();
}
return qsl("[ ") + inDialogsText() + qsl(" ]");
}
@ -5377,12 +5407,15 @@ HistoryTextState HistoryContact::getState(int x, int y, HistoryStateRequest requ
return result;
}
const QString HistoryContact::inDialogsText() const {
QString HistoryContact::inDialogsText() const {
return lang(lng_in_dlg_contact);
}
const QString HistoryContact::inHistoryText() const {
return qsl("[ ") + lang(lng_in_dlg_contact) + qsl(" : ") + _name.original() + qsl(", ") + _phone + qsl(" ]");
QString HistoryContact::selectedText(TextSelection selection) const {
if (selection != FullSelection) {
return QString();
}
return qsl("[ ") + lang(lng_in_dlg_contact) + qsl(" ]\n") + _name.original() + '\n' + _phone;
}
void HistoryContact::attachToParent() {
@ -5895,13 +5928,23 @@ void HistoryWebPage::detachFromParent() {
if (_attach) _attach->detachFromParent();
}
const QString HistoryWebPage::inDialogsText() const {
QString HistoryWebPage::inDialogsText() const {
return QString();
}
const QString HistoryWebPage::inHistoryText() const {
QString HistoryWebPage::selectedText(TextSelection selection) const {
if (selection == FullSelection) {
return QString();
}
auto titleResult = _title.original(selection, Text::ExpandLinksAll);
auto descriptionResult = _description.original(toDescriptionSelection(selection), Text::ExpandLinksAll);
if (titleResult.isEmpty()) {
return descriptionResult;
} else if (descriptionResult.isEmpty()) {
return titleResult;
}
return titleResult + '\n' + descriptionResult;
}
ImagePtr HistoryWebPage::replyPreview() {
return _attach ? _attach->replyPreview() : (_data->photo ? _data->photo->makeReplyPreview() : ImagePtr());
@ -6263,6 +6306,7 @@ void HistoryLocation::draw(Painter &p, const QRect &r, TextSelection selection,
HistoryTextState HistoryLocation::getState(int x, int y, HistoryStateRequest request) const {
HistoryTextState result;
auto symbolAdd = 0;
if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return result;
int32 skipx = 0, skipy = 0, width = _width, height = _height;
@ -6286,6 +6330,8 @@ HistoryTextState HistoryLocation::getState(int x, int y, HistoryStateRequest req
if (y >= skipy && y < skipy + titleh) {
result = _title.getStateLeft(x - skipx - st::msgPadding.left(), y - skipy, textw, _width, request.forText());
return result;
} else if (y >= skipy + titleh) {
symbolAdd += _title.length();
}
skipy += titleh;
}
@ -6293,8 +6339,8 @@ HistoryTextState HistoryLocation::getState(int x, int y, HistoryStateRequest req
auto descriptionh = qMin(_description.countHeight(textw), 3 * st::webPageDescriptionFont->height);
if (y >= skipy && y < skipy + descriptionh) {
result = _description.getStateLeft(x - skipx - st::msgPadding.left(), y - skipy, textw, _width, request.forText());
if (!_title.isEmpty()) result.symbol += _title.length();
return result;
} else if (y >= skipy + descriptionh) {
symbolAdd += _description.length();
}
skipy += descriptionh;
}
@ -6311,9 +6357,8 @@ HistoryTextState HistoryLocation::getState(int x, int y, HistoryStateRequest req
if (inDate) {
result.cursor = HistoryInDateCursorState;
}
return result;
}
result.symbol += symbolAdd;
return result;
}
@ -6329,12 +6374,25 @@ TextSelection HistoryLocation::adjustSelection(TextSelection selection, TextSele
return { titleSelection.from, fromDescriptionSelection(descriptionSelection).to };
}
const QString HistoryLocation::inDialogsText() const {
return lang(lng_maps_point);
QString HistoryLocation::inDialogsText() const {
return _title.isEmpty() ? lang(lng_maps_point) : _title.original(AllTextSelection);
}
const QString HistoryLocation::inHistoryText() const {
return qsl("[ ") + lang(lng_maps_point) + qsl(" : ") + _link->text() + qsl(" ]");
QString HistoryLocation::selectedText(TextSelection selection) const {
if (selection == FullSelection) {
auto result = qsl("[ ") + lang(lng_maps_point) + qsl(" ]\n");
auto info = selectedText(AllTextSelection);
if (!info.isEmpty()) result.append(info).append('\n');
return result + _link->text();
}
auto titleResult = _title.original(selection);
auto descriptionResult = _description.original(toDescriptionSelection(selection));
if (titleResult.isEmpty()) {
return descriptionResult;
} else if (descriptionResult.isEmpty()) {
return titleResult;
}
return titleResult + '\n' + descriptionResult;
}
int32 HistoryLocation::fullWidth() const {
@ -7044,12 +7102,21 @@ void HistoryMessage::eraseFromOverview() {
}
QString HistoryMessage::selectedText(TextSelection selection) const {
QString result;
if (_media && selection == FullSelection) {
QString text = _text.original(AllTextSelection, Text::ExpandLinksAll), mediaText = _media->inHistoryText();
result = text.isEmpty() ? mediaText : (mediaText.isEmpty() ? text : (text + ' ' + mediaText));
QString result, textResult, mediaResult;
if (selection == FullSelection) {
textResult = _text.original(AllTextSelection, Text::ExpandLinksAll);
} else {
result = _text.original((selection == FullSelection) ? AllTextSelection : selection, Text::ExpandLinksAll);
textResult = _text.original(selection, Text::ExpandLinksAll);
}
if (_media) {
mediaResult = _media->selectedText(toMediaSelection(selection));
}
if (textResult.isEmpty()) {
result = mediaResult;
} else if (mediaResult.isEmpty()) {
result = textResult;
} else {
result = textResult + qstr("\n\n") + mediaResult;
}
if (auto fwd = Get<HistoryMessageForwarded>()) {
if (selection == FullSelection) {

View file

@ -1082,6 +1082,14 @@ private:
};
namespace internal {
TextSelection unshiftSelection(TextSelection selection, const Text &byText);
TextSelection shiftSelection(TextSelection selection, const Text &byText);
} // namespace internal
class HistoryItem : public HistoryElem, public Composer, public ClickHandlerHost {
public:
@ -1539,10 +1547,10 @@ protected:
}
TextSelection toMediaSelection(TextSelection selection) const {
return unshiftSelection(selection, _text);
return internal::unshiftSelection(selection, _text);
}
TextSelection fromMediaSelection(TextSelection selection) const {
return shiftSelection(selection, _text);
return internal::shiftSelection(selection, _text);
}
Text _text = { int(st::msgMinWidth) };
@ -1640,8 +1648,8 @@ public:
HistoryMedia &operator=(const HistoryMedia &other) = delete;
virtual HistoryMediaType type() const = 0;
virtual const QString inDialogsText() const = 0;
virtual const QString inHistoryText() const = 0;
virtual QString inDialogsText() const = 0;
virtual QString selectedText(TextSelection selection) const = 0;
bool hasPoint(int x, int y) const {
return (x >= 0 && y >= 0 && x < _width && y < _height);
@ -1871,8 +1879,8 @@ public:
return _caption.adjustSelection(selection, type);
}
const QString inDialogsText() const override;
const QString inHistoryText() const override;
QString inDialogsText() const override;
QString selectedText(TextSelection selection) const override;
PhotoData *photo() const {
return _data;
@ -1948,8 +1956,8 @@ public:
return _caption.adjustSelection(selection, type);
}
const QString inDialogsText() const override;
const QString inHistoryText() const override;
QString inDialogsText() const override;
QString selectedText(TextSelection selection) const override;
DocumentData *getDocument() override {
return _data;
@ -2068,8 +2076,8 @@ public:
return selection;
}
const QString inDialogsText() const override;
const QString inHistoryText() const override;
QString inDialogsText() const override;
QString selectedText(TextSelection selection) const override;
bool uploading() const override {
return _data->uploading();
@ -2152,8 +2160,8 @@ public:
return _caption.adjustSelection(selection, type);
}
const QString inDialogsText() const override;
const QString inHistoryText() const override;
QString inDialogsText() const override;
QString selectedText(TextSelection selection) const override;
bool uploading() const override {
return _data->uploading();
@ -2250,8 +2258,8 @@ public:
return true;
}
const QString inDialogsText() const override;
const QString inHistoryText() const override;
QString inDialogsText() const override;
QString selectedText(TextSelection selection) const override;
DocumentData *getDocument() override {
return _data;
@ -2319,8 +2327,8 @@ public:
return true;
}
const QString inDialogsText() const override;
const QString inHistoryText() const override;
QString inDialogsText() const override;
QString selectedText(TextSelection selection) const override;
void attachToParent() override;
void detachFromParent() override;
@ -2384,8 +2392,8 @@ public:
return _attach && _attach->dragItemByHandler(p);
}
const QString inDialogsText() const override;
const QString inHistoryText() const override;
QString inDialogsText() const override;
QString selectedText(TextSelection selection) const override;
void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override;
void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override;
@ -2433,10 +2441,10 @@ public:
private:
TextSelection toDescriptionSelection(TextSelection selection) const {
return unshiftSelection(selection, _title);
return internal::unshiftSelection(selection, _title);
}
TextSelection fromDescriptionSelection(TextSelection selection) const {
return shiftSelection(selection, _title);
return internal::shiftSelection(selection, _title);
}
WebPageData *_data;
@ -2515,8 +2523,8 @@ public:
return p == _link;
}
const QString inDialogsText() const override;
const QString inHistoryText() const override;
QString inDialogsText() const override;
QString selectedText(TextSelection selection) const override;
bool needsBubble() const override {
if (!_title.isEmpty() || !_description.isEmpty()) {
@ -2533,10 +2541,10 @@ public:
private:
TextSelection toDescriptionSelection(TextSelection selection) const {
return unshiftSelection(selection, _title);
return internal::unshiftSelection(selection, _title);
}
TextSelection fromDescriptionSelection(TextSelection selection) const {
return shiftSelection(selection, _title);
return internal::shiftSelection(selection, _title);
}
LocationData *_data;

View file

@ -1066,7 +1066,7 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
}
}
QString contextMenuText = item->selectedText(FullSelection);
if (!contextMenuText.isEmpty() && (!msg || !msg->getMedia() || (msg->getMedia()->type() != MediaTypeSticker && msg->getMedia()->type() != MediaTypeGif))) {
if (!contextMenuText.isEmpty() && msg && !msg->getMedia()) {
_menu->addAction(lang(lng_context_copy_text), this, SLOT(copyContextText()))->setEnabled(true);
}
}

View file

@ -21,7 +21,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
#pragma once
#include "inline_bots/inline_bot_layout_item.h"
#include "ui/text.h"
#include "ui/text/text.h"
namespace InlineBots {
namespace Layout {

View file

@ -22,7 +22,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
#include "layout.h"
#include "structs.h"
#include "ui/text.h"
#include "ui/text/text.h"
namespace InlineBots {
class Result;

View file

@ -33,7 +33,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
#include "mainwidget.h"
#include "mainwindow.h"
#include "application.h"
#include "ui/text.h"
#include "ui/text/text.h"
namespace {
IntroWidget *signalEmitOn = 0;

View file

@ -27,7 +27,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
#include "passcodewidget.h"
#include "mainwindow.h"
#include "application.h"
#include "ui/text.h"
#include "ui/text/text.h"
PasscodeWidget::PasscodeWidget(QWidget *parent) : TWidget(parent)
, _a_show(animation(this, &PasscodeWidget::step_show))

View file

@ -46,7 +46,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
#include "ui/popupmenu.h"
#include "ui/scrollarea.h"
#include "ui/images.h"
#include "ui/text.h"
#include "ui/text/text.h"
#include "ui/flatlabel.h"
#include "app.h"

View file

@ -20,7 +20,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
*/
#pragma once
#include "ui/text.h"
#include "ui/text/text.h"
void emojiInit();
EmojiPtr emojiGet(uint32 code);

View file

@ -17,7 +17,7 @@
*/
#pragma once
#include "text.h"
#include "ui/text/text.h"
class PopupMenu : public TWidget {
Q_OBJECT

View file

@ -20,296 +20,11 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
*/
#pragma once
#include "core/click_handler.h"
enum EntityInTextType {
EntityInTextUrl,
EntityInTextCustomUrl,
EntityInTextEmail,
EntityInTextHashtag,
EntityInTextMention,
EntityInTextBotCommand,
EntityInTextBold,
EntityInTextItalic,
EntityInTextCode, // inline
EntityInTextPre, // block
};
struct EntityInText {
EntityInText(EntityInTextType type, int offset, int length, const QString &text = QString()) : type(type), offset(offset), length(length), text(text) {
}
EntityInTextType type;
int offset, length;
QString text;
};
typedef QList<EntityInText> EntitiesInText;
// text preprocess
QString textClean(const QString &text);
QString textRichPrepare(const QString &text);
QString textOneLine(const QString &text, bool trim = true, bool rich = false);
QString textAccentFold(const QString &text);
QString textSearchKey(const QString &text);
bool textSplit(QString &sendingText, EntitiesInText &sendingEntities, QString &leftText, EntitiesInText &leftEntities, int32 limit);
enum {
TextParseMultiline = 0x001,
TextParseLinks = 0x002,
TextParseRichText = 0x004,
TextParseMentions = 0x008,
TextParseHashtags = 0x010,
TextParseBotCommands = 0x020,
TextParseMono = 0x040,
TextTwitterMentions = 0x100,
TextTwitterHashtags = 0x200,
TextInstagramMentions = 0x400,
TextInstagramHashtags = 0x800,
};
inline EntitiesInText entitiesFromMTP(const QVector<MTPMessageEntity> &entities) {
EntitiesInText result;
if (!entities.isEmpty()) {
result.reserve(entities.size());
for (int32 i = 0, l = entities.size(); i != l; ++i) {
const auto &e(entities.at(i));
switch (e.type()) {
case mtpc_messageEntityUrl: { const auto &d(e.c_messageEntityUrl()); result.push_back(EntityInText(EntityInTextUrl, d.voffset.v, d.vlength.v)); } break;
case mtpc_messageEntityTextUrl: { const auto &d(e.c_messageEntityTextUrl()); result.push_back(EntityInText(EntityInTextCustomUrl, d.voffset.v, d.vlength.v, textClean(qs(d.vurl)))); } break;
case mtpc_messageEntityEmail: { const auto &d(e.c_messageEntityEmail()); result.push_back(EntityInText(EntityInTextEmail, d.voffset.v, d.vlength.v)); } break;
case mtpc_messageEntityHashtag: { const auto &d(e.c_messageEntityHashtag()); result.push_back(EntityInText(EntityInTextHashtag, d.voffset.v, d.vlength.v)); } break;
case mtpc_messageEntityMention: { const auto &d(e.c_messageEntityMention()); result.push_back(EntityInText(EntityInTextMention, d.voffset.v, d.vlength.v)); } break;
case mtpc_messageEntityBotCommand: { const auto &d(e.c_messageEntityBotCommand()); result.push_back(EntityInText(EntityInTextBotCommand, d.voffset.v, d.vlength.v)); } break;
case mtpc_messageEntityBold: { const auto &d(e.c_messageEntityBold()); result.push_back(EntityInText(EntityInTextBold, d.voffset.v, d.vlength.v)); } break;
case mtpc_messageEntityItalic: { const auto &d(e.c_messageEntityItalic()); result.push_back(EntityInText(EntityInTextItalic, d.voffset.v, d.vlength.v)); } break;
case mtpc_messageEntityCode: { const auto &d(e.c_messageEntityCode()); result.push_back(EntityInText(EntityInTextCode, d.voffset.v, d.vlength.v)); } break;
case mtpc_messageEntityPre: { const auto &d(e.c_messageEntityPre()); result.push_back(EntityInText(EntityInTextPre, d.voffset.v, d.vlength.v, textClean(qs(d.vlanguage)))); } break;
}
}
}
return result;
}
inline MTPVector<MTPMessageEntity> linksToMTP(const EntitiesInText &links, bool sending = false) {
MTPVector<MTPMessageEntity> result(MTP_vector<MTPMessageEntity>(0));
QVector<MTPMessageEntity> &v(result._vector().v);
for (int32 i = 0, s = links.size(); i != s; ++i) {
const EntityInText &l(links.at(i));
if (l.length <= 0 || (sending && l.type != EntityInTextCode && l.type != EntityInTextPre)) continue;
switch (l.type) {
case EntityInTextUrl: v.push_back(MTP_messageEntityUrl(MTP_int(l.offset), MTP_int(l.length))); break;
case EntityInTextCustomUrl: v.push_back(MTP_messageEntityTextUrl(MTP_int(l.offset), MTP_int(l.length), MTP_string(l.text))); break;
case EntityInTextEmail: v.push_back(MTP_messageEntityEmail(MTP_int(l.offset), MTP_int(l.length))); break;
case EntityInTextHashtag: v.push_back(MTP_messageEntityHashtag(MTP_int(l.offset), MTP_int(l.length))); break;
case EntityInTextMention: v.push_back(MTP_messageEntityMention(MTP_int(l.offset), MTP_int(l.length))); break;
case EntityInTextBotCommand: v.push_back(MTP_messageEntityBotCommand(MTP_int(l.offset), MTP_int(l.length))); break;
case EntityInTextBold: v.push_back(MTP_messageEntityBold(MTP_int(l.offset), MTP_int(l.length))); break;
case EntityInTextItalic: v.push_back(MTP_messageEntityItalic(MTP_int(l.offset), MTP_int(l.length))); break;
case EntityInTextCode: v.push_back(MTP_messageEntityCode(MTP_int(l.offset), MTP_int(l.length))); break;
case EntityInTextPre: v.push_back(MTP_messageEntityPre(MTP_int(l.offset), MTP_int(l.length), MTP_string(l.text))); break;
}
}
return result;
}
EntitiesInText textParseEntities(QString &text, int32 flags, bool rich = false); // changes text if (flags & TextParseMono)
QString textApplyEntities(const QString &text, const EntitiesInText &entities);
#include "ui/emoji_config.h"
void emojiDraw(QPainter &p, EmojiPtr e, int x, int y);
#include "../../../QtStatic/qtbase/src/gui/text/qfontengine_p.h"
enum TextBlockType {
TextBlockTNewline = 0x01,
TextBlockTText = 0x02,
TextBlockTEmoji = 0x03,
TextBlockTSkip = 0x04,
};
enum TextBlockFlags {
TextBlockFBold = 0x01,
TextBlockFItalic = 0x02,
TextBlockFUnderline = 0x04,
TextBlockFTilde = 0x08, // tilde fix in OpenSans
TextBlockFSemibold = 0x10,
TextBlockFCode = 0x20,
TextBlockFPre = 0x40,
};
class ITextBlock {
public:
ITextBlock(const style::font &font, const QString &str, uint16 from, uint16 length, uchar flags, const style::color &color, uint16 lnkIndex) : _from(from), _flags((flags & 0xFF) | ((lnkIndex & 0xFFFF) << 12))/*, _color(color)*/, _lpadding(0) {
if (length) {
if (str.at(_from + length - 1).unicode() == QChar::Space) {
_rpadding = font->spacew;
}
if (length > 1 && str.at(0).unicode() == QChar::Space) {
_lpadding = font->spacew;
}
}
}
uint16 from() const {
return _from;
}
int32 width() const {
return _width.toInt();
}
int32 lpadding() const {
return _lpadding.toInt();
}
int32 rpadding() const {
return _rpadding.toInt();
}
QFixed f_width() const {
return _width;
}
QFixed f_lpadding() const {
return _lpadding;
}
QFixed f_rpadding() const {
return _rpadding;
}
uint16 lnkIndex() const {
return (_flags >> 12) & 0xFFFF;
}
void setLnkIndex(uint16 lnkIndex) {
_flags = (_flags & ~(0xFFFF << 12)) | (lnkIndex << 12);
}
TextBlockType type() const {
return TextBlockType((_flags >> 8) & 0x0F);
}
int32 flags() const {
return (_flags & 0xFF);
}
const style::color &color() const {
static style::color tmp;
return tmp;//_color;
}
virtual ITextBlock *clone() const = 0;
virtual ~ITextBlock() {
}
protected:
uint16 _from;
uint32 _flags; // 4 bits empty, 16 bits lnkIndex, 4 bits type, 8 bits flags
QFixed _width, _lpadding, _rpadding;
};
class NewlineBlock : public ITextBlock {
public:
Qt::LayoutDirection nextDirection() const {
return _nextDir;
}
ITextBlock *clone() const {
return new NewlineBlock(*this);
}
private:
NewlineBlock(const style::font &font, const QString &str, uint16 from, uint16 length) : ITextBlock(font, str, from, length, 0, st::transparent, 0), _nextDir(Qt::LayoutDirectionAuto) {
_flags |= ((TextBlockTNewline & 0x0F) << 8);
}
Qt::LayoutDirection _nextDir;
friend class Text;
friend class TextParser;
friend class TextPainter;
};
struct TextWord {
TextWord() {
}
TextWord(uint16 from, QFixed width, QFixed rbearing, QFixed rpadding = 0) : from(from),
_rbearing(rbearing.value() > 0x7FFF ? 0x7FFF : (rbearing.value() < -0x7FFF ? -0x7FFF : rbearing.value())), width(width), rpadding(rpadding) {
}
QFixed f_rbearing() const {
return QFixed::fromFixed(_rbearing);
}
uint16 from;
int16 _rbearing;
QFixed width, rpadding;
};
class TextBlock : public ITextBlock {
public:
QFixed f_rbearing() const {
return _words.isEmpty() ? 0 : _words.back().f_rbearing();
}
ITextBlock *clone() const {
return new TextBlock(*this);
}
private:
TextBlock(const style::font &font, const QString &str, QFixed minResizeWidth, uint16 from, uint16 length, uchar flags, const style::color &color, uint16 lnkIndex);
typedef QVector<TextWord> TextWords;
TextWords _words;
friend class Text;
friend class TextParser;
friend class BlockParser;
friend class TextPainter;
};
class EmojiBlock : public ITextBlock {
public:
ITextBlock *clone() const {
return new EmojiBlock(*this);
}
private:
EmojiBlock(const style::font &font, const QString &str, uint16 from, uint16 length, uchar flags, const style::color &color, uint16 lnkIndex, const EmojiData *emoji);
const EmojiData *emoji;
friend class Text;
friend class TextParser;
friend class TextPainter;
};
class SkipBlock : public ITextBlock {
public:
int32 height() const {
return _height;
}
ITextBlock *clone() const {
return new SkipBlock(*this);
}
private:
SkipBlock(const style::font &font, const QString &str, uint16 from, int32 w, int32 h, uint16 lnkIndex);
int32 _height;
friend class Text;
friend class TextParser;
friend class TextPainter;
};
#include "core/click_handler.h"
#include "ui/text/text_entity.h"
#include "ui/emoji_config.h"
static const QChar TextCommand(0x0010);
enum TextCommands {
@ -349,6 +64,9 @@ struct TextSelection {
}
constexpr TextSelection(uint16 from, uint16 to) : from(from), to(to) {
}
constexpr bool empty() const {
return from == to;
}
uint16 from : 16;
uint16 to : 16;
};
@ -364,6 +82,7 @@ static constexpr TextSelection AllTextSelection = { 0, 0xFFFF };
typedef QPair<QString, QString> TextCustomTag; // open str and close str
typedef QMap<QChar, TextCustomTag> TextCustomTagsMap;
class ITextBlock;
class Text {
public:
@ -383,9 +102,7 @@ public:
void setLink(uint16 lnkIndex, const ClickHandlerPtr &lnk);
bool hasLinks() const;
bool hasSkipBlock() const {
return _blocks.isEmpty() ? false : _blocks.back()->type() == TextBlockTSkip;
}
bool hasSkipBlock() const;
void setSkipBlock(int32 width, int32 height);
void removeSkipBlock();
@ -462,7 +179,7 @@ public:
ExpandLinksShortened,
ExpandLinksAll,
};
QString original(TextSelection selection = { 0, 0xFFFF }, ExpandLinksMode mode = ExpandLinksShortened) const;
QString original(TextSelection selection = AllTextSelection, ExpandLinksMode mode = ExpandLinksShortened) const;
EntitiesInText originalEntities() const;
bool lastDots(int32 dots, int32 maxdots = 3) { // hack for typing animation
@ -539,9 +256,8 @@ const QRegularExpression &reBotCommand();
// text style
const style::textStyle *textstyleCurrent();
void textstyleSet(const style::textStyle *style);
inline void textstyleRestore() {
textstyleSet(0);
textstyleSet(nullptr);
}
// textcmd
@ -681,96 +397,4 @@ inline QString myUrlDecode(const QString &enc) {
return QUrl::fromPercentEncoding(enc.toUtf8());
}
QString prepareTextWithEntities(QString result, EntitiesInText &entities, int32 flags);
inline QString prepareText(QString result, bool checkLinks = false) {
EntitiesInText entities;
return prepareTextWithEntities(result, entities, checkLinks ? (TextParseLinks | TextParseMentions | TextParseHashtags | TextParseBotCommands) : 0);
}
inline void moveStringPart(QChar *start, int32 &to, int32 &from, int32 count, EntitiesInText &entities) {
if (count > 0) {
if (to < from) {
memmove(start + to, start + from, count * sizeof(QChar));
for (EntitiesInText::iterator i = entities.begin(), e = entities.end(); i != e; ++i) {
if (i->offset >= from + count) break;
if (i->offset + i->length < from) continue;
if (i->offset >= from) {
i->offset -= (from - to);
i->length += (from - to);
}
if (i->offset + i->length < from + count) {
i->length -= (from - to);
}
}
}
to += count;
from += count;
}
}
// replace bad symbols with space and remove \r
inline void cleanTextWithEntities(QString &result, EntitiesInText &entities) {
result = result.replace('\t', qstr(" "));
int32 len = result.size(), to = 0, from = 0;
QChar *start = result.data();
for (QChar *ch = start, *end = start + len; ch < end; ++ch) {
if (ch->unicode() == '\r') {
moveStringPart(start, to, from, (ch - start) - from, entities);
++from;
} else if (chReplacedBySpace(*ch)) {
*ch = ' ';
}
}
moveStringPart(start, to, from, len - from, entities);
if (to < len) result.resize(to);
}
inline void trimTextWithEntities(QString &result, EntitiesInText &entities) {
bool foundNotTrimmed = false;
for (QChar *s = result.data(), *e = s + result.size(), *ch = e; ch != s;) { // rtrim
--ch;
if (!chIsTrimmed(*ch)) {
if (ch + 1 < e) {
int32 l = ch + 1 - s;
for (EntitiesInText::iterator i = entities.begin(), e = entities.end(); i != e; ++i) {
if (i->offset > l) {
i->offset = l;
i->length = 0;
} else if (i->offset + i->length > l) {
i->length = l - i->offset;
}
}
result.resize(l);
}
foundNotTrimmed = true;
break;
}
}
if (!foundNotTrimmed) {
result.clear();
entities.clear();
return;
}
for (QChar *s = result.data(), *ch = s, *e = s + result.size(); ch != e; ++ch) { // ltrim
if (!chIsTrimmed(*ch)) {
if (ch > s) {
int32 l = ch - s;
for (EntitiesInText::iterator i = entities.begin(), e = entities.end(); i != e; ++i) {
if (i->offset + i->length <= l) {
i->length = 0;
i->offset = 0;
} else if (i->offset < l) {
i->length = i->offset + i->length - l;
i->offset = 0;
} else {
i->offset -= l;
}
}
result = result.mid(l);
}
break;
}
}
}
void emojiDraw(QPainter &p, EmojiPtr e, int x, int y);

View file

@ -0,0 +1,322 @@
/*
This file is part of Telegram Desktop,
the official desktop version of Telegram messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
It is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
*/
#include "stdafx.h"
#include "ui/text/text_block.h"
// COPIED FROM qtextlayout.cpp AND MODIFIED
namespace {
struct ScriptLine {
ScriptLine() : length(0), textWidth(0) {
}
int32 length;
QFixed textWidth;
};
struct LineBreakHelper
{
LineBreakHelper()
: glyphCount(0), maxGlyphs(0), currentPosition(0), fontEngine(0), logClusters(0)
{
}
ScriptLine tmpData;
ScriptLine spaceData;
QGlyphLayout glyphs;
int glyphCount;
int maxGlyphs;
int currentPosition;
glyph_t previousGlyph;
QFixed rightBearing;
QFontEngine *fontEngine;
const unsigned short *logClusters;
inline glyph_t currentGlyph() const
{
Q_ASSERT(currentPosition > 0);
Q_ASSERT(logClusters[currentPosition - 1] < glyphs.numGlyphs);
return glyphs.glyphs[logClusters[currentPosition - 1]];
}
inline void saveCurrentGlyph()
{
previousGlyph = 0;
if (currentPosition > 0 &&
logClusters[currentPosition - 1] < glyphs.numGlyphs) {
previousGlyph = currentGlyph(); // needed to calculate right bearing later
}
}
inline void adjustRightBearing(glyph_t glyph)
{
qreal rb;
fontEngine->getGlyphBearings(glyph, 0, &rb);
rightBearing = qMin(QFixed(), QFixed::fromReal(rb));
}
inline void adjustRightBearing()
{
if (currentPosition <= 0)
return;
adjustRightBearing(currentGlyph());
}
inline void adjustPreviousRightBearing()
{
if (previousGlyph > 0)
adjustRightBearing(previousGlyph);
}
};
static inline void addNextCluster(int &pos, int end, ScriptLine &line, int &glyphCount,
const QScriptItem &current, const unsigned short *logClusters,
const QGlyphLayout &glyphs)
{
int glyphPosition = logClusters[pos];
do { // got to the first next cluster
++pos;
++line.length;
} while (pos < end && logClusters[pos] == glyphPosition);
do { // calculate the textWidth for the rest of the current cluster.
if (!glyphs.attributes[glyphPosition].dontPrint)
line.textWidth += glyphs.advances[glyphPosition];
++glyphPosition;
} while (glyphPosition < current.num_glyphs && !glyphs.attributes[glyphPosition].clusterStart);
Q_ASSERT((pos == end && glyphPosition == current.num_glyphs) || logClusters[pos] == glyphPosition);
++glyphCount;
}
} // anonymous namespace
class BlockParser {
public:
BlockParser(QTextEngine *e, TextBlock *b, QFixed minResizeWidth, int32 blockFrom, const QString &str)
: block(b), eng(e), str(str) {
parseWords(minResizeWidth, blockFrom);
}
void parseWords(QFixed minResizeWidth, int32 blockFrom) {
LineBreakHelper lbh;
lbh.maxGlyphs = INT_MAX;
int item = -1;
int newItem = eng->findItem(0);
style::align alignment = eng->option.alignment();
const QCharAttributes *attributes = eng->attributes();
if (!attributes)
return;
lbh.currentPosition = 0;
int end = 0;
lbh.logClusters = eng->layoutData->logClustersPtr;
lbh.previousGlyph = 0;
block->_lpadding = 0;
block->_words.clear();
int wordStart = lbh.currentPosition;
bool addingEachGrapheme = false;
int lastGraphemeBoundaryPosition = -1;
ScriptLine lastGraphemeBoundaryLine;
while (newItem < eng->layoutData->items.size()) {
if (newItem != item) {
item = newItem;
const QScriptItem &current = eng->layoutData->items[item];
if (!current.num_glyphs) {
eng->shape(item);
attributes = eng->attributes();
if (!attributes)
return;
lbh.logClusters = eng->layoutData->logClustersPtr;
}
lbh.currentPosition = current.position;
end = current.position + eng->length(item);
lbh.glyphs = eng->shapedGlyphs(&current);
QFontEngine *fontEngine = eng->fontEngine(current);
if (lbh.fontEngine != fontEngine) {
lbh.fontEngine = fontEngine;
}
}
const QScriptItem &current = eng->layoutData->items[item];
if (attributes[lbh.currentPosition].whiteSpace) {
while (lbh.currentPosition < end && attributes[lbh.currentPosition].whiteSpace)
addNextCluster(lbh.currentPosition, end, lbh.spaceData, lbh.glyphCount,
current, lbh.logClusters, lbh.glyphs);
if (block->_words.isEmpty()) {
block->_lpadding = lbh.spaceData.textWidth;
} else {
block->_words.back().rpadding += lbh.spaceData.textWidth;
block->_width += lbh.spaceData.textWidth;
}
lbh.spaceData.length = 0;
lbh.spaceData.textWidth = 0;
wordStart = lbh.currentPosition;
addingEachGrapheme = false;
lastGraphemeBoundaryPosition = -1;
lastGraphemeBoundaryLine = ScriptLine();
} else {
do {
addNextCluster(lbh.currentPosition, end, lbh.tmpData, lbh.glyphCount,
current, lbh.logClusters, lbh.glyphs);
if (lbh.currentPosition >= eng->layoutData->string.length()
|| attributes[lbh.currentPosition].whiteSpace
|| isLineBreak(attributes, lbh.currentPosition)) {
lbh.adjustRightBearing();
block->_words.push_back(TextWord(wordStart + blockFrom, lbh.tmpData.textWidth, qMin(QFixed(), lbh.rightBearing)));
block->_width += lbh.tmpData.textWidth;
lbh.tmpData.textWidth = 0;
lbh.tmpData.length = 0;
wordStart = lbh.currentPosition;
break;
} else if (attributes[lbh.currentPosition].graphemeBoundary) {
if (!addingEachGrapheme && lbh.tmpData.textWidth > minResizeWidth) {
if (lastGraphemeBoundaryPosition >= 0) {
lbh.adjustPreviousRightBearing();
block->_words.push_back(TextWord(wordStart + blockFrom, -lastGraphemeBoundaryLine.textWidth, qMin(QFixed(), lbh.rightBearing)));
block->_width += lastGraphemeBoundaryLine.textWidth;
lbh.tmpData.textWidth -= lastGraphemeBoundaryLine.textWidth;
lbh.tmpData.length -= lastGraphemeBoundaryLine.length;
wordStart = lastGraphemeBoundaryPosition;
}
addingEachGrapheme = true;
}
if (addingEachGrapheme) {
lbh.adjustRightBearing();
block->_words.push_back(TextWord(wordStart + blockFrom, -lbh.tmpData.textWidth, qMin(QFixed(), lbh.rightBearing)));
block->_width += lbh.tmpData.textWidth;
lbh.tmpData.textWidth = 0;
lbh.tmpData.length = 0;
wordStart = lbh.currentPosition;
} else {
lastGraphemeBoundaryPosition = lbh.currentPosition;
lastGraphemeBoundaryLine = lbh.tmpData;
lbh.saveCurrentGlyph();
}
}
} while (lbh.currentPosition < end);
}
if (lbh.currentPosition == end)
newItem = item + 1;
}
if (block->_words.isEmpty()) {
block->_rpadding = 0;
} else {
block->_rpadding = block->_words.back().rpadding;
block->_width -= block->_rpadding;
block->_words.squeeze();
}
}
bool isLineBreak(const QCharAttributes *attributes, int32 index) {
bool lineBreak = attributes[index].lineBreak;
if (lineBreak && block->lnkIndex() > 0 && index > 0 && str.at(index - 1) == '/') {
return false; // don't break after / in links
}
return lineBreak;
}
private:
TextBlock *block;
QTextEngine *eng;
const QString &str;
};
TextBlock::TextBlock(const style::font &font, const QString &str, QFixed minResizeWidth, uint16 from, uint16 length, uchar flags, const style::color &color, uint16 lnkIndex) : ITextBlock(font, str, from, length, flags, color, lnkIndex) {
_flags |= ((TextBlockTText & 0x0F) << 8);
if (length) {
style::font blockFont = font;
if (!flags && lnkIndex) {
// should use textStyle lnkFlags somehow... not supported
}
if ((flags & TextBlockFPre) || (flags & TextBlockFCode)) {
blockFont = App::monofont();
if (blockFont->size() != font->size() || blockFont->flags() != font->flags()) {
blockFont = style::font(font->size(), font->flags(), blockFont->family());
}
} else {
if (flags & TextBlockFBold) {
blockFont = blockFont->bold();
} else if (flags & TextBlockFSemibold) {
blockFont = st::semiboldFont;
if (blockFont->size() != font->size() || blockFont->flags() != font->flags()) {
blockFont = style::font(font->size(), font->flags(), blockFont->family());
}
}
if (flags & TextBlockFItalic) blockFont = blockFont->italic();
if (flags & TextBlockFUnderline) blockFont = blockFont->underline();
if (flags & TextBlockFTilde) { // tilde fix in OpenSans
blockFont = st::semiboldFont;
}
}
QString part = str.mid(_from, length);
QStackTextEngine engine(part, blockFont->f);
engine.itemize();
QTextLayout layout(&engine);
layout.beginLayout();
layout.createLine();
bool logCrashString = (rand_value<uchar>() % 4 == 1);
if (logCrashString) {
SignalHandlers::setCrashAnnotationRef("CrashString", &str);
}
BlockParser parser(&engine, this, minResizeWidth, _from, part);
if (logCrashString) {
SignalHandlers::clearCrashAnnotationRef("CrashString");
}
layout.endLayout();
}
}
EmojiBlock::EmojiBlock(const style::font &font, const QString &str, uint16 from, uint16 length, uchar flags, const style::color &color, uint16 lnkIndex, const EmojiData *emoji) : ITextBlock(font, str, from, length, flags, color, lnkIndex), emoji(emoji) {
_flags |= ((TextBlockTEmoji & 0x0F) << 8);
_width = int(st::emojiSize + 2 * st::emojiPadding);
}
SkipBlock::SkipBlock(const style::font &font, const QString &str, uint16 from, int32 w, int32 h, uint16 lnkIndex) : ITextBlock(font, str, from, 1, 0, style::color(), lnkIndex), _height(h) {
_flags |= ((TextBlockTSkip & 0x0F) << 8);
_width = w;
}

View file

@ -0,0 +1,214 @@
/*
This file is part of Telegram Desktop,
the official desktop version of Telegram messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
It is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
*/
#pragma once
#include "../../../QtStatic/qtbase/src/gui/text/qfontengine_p.h"
enum TextBlockType {
TextBlockTNewline = 0x01,
TextBlockTText = 0x02,
TextBlockTEmoji = 0x03,
TextBlockTSkip = 0x04,
};
enum TextBlockFlags {
TextBlockFBold = 0x01,
TextBlockFItalic = 0x02,
TextBlockFUnderline = 0x04,
TextBlockFTilde = 0x08, // tilde fix in OpenSans
TextBlockFSemibold = 0x10,
TextBlockFCode = 0x20,
TextBlockFPre = 0x40,
};
class ITextBlock {
public:
ITextBlock(const style::font &font, const QString &str, uint16 from, uint16 length, uchar flags, const style::color &color, uint16 lnkIndex) : _from(from), _flags((flags & 0xFF) | ((lnkIndex & 0xFFFF) << 12))/*, _color(color)*/, _lpadding(0) {
if (length) {
if (str.at(_from + length - 1).unicode() == QChar::Space) {
_rpadding = font->spacew;
}
if (length > 1 && str.at(0).unicode() == QChar::Space) {
_lpadding = font->spacew;
}
}
}
uint16 from() const {
return _from;
}
int32 width() const {
return _width.toInt();
}
int32 lpadding() const {
return _lpadding.toInt();
}
int32 rpadding() const {
return _rpadding.toInt();
}
QFixed f_width() const {
return _width;
}
QFixed f_lpadding() const {
return _lpadding;
}
QFixed f_rpadding() const {
return _rpadding;
}
uint16 lnkIndex() const {
return (_flags >> 12) & 0xFFFF;
}
void setLnkIndex(uint16 lnkIndex) {
_flags = (_flags & ~(0xFFFF << 12)) | (lnkIndex << 12);
}
TextBlockType type() const {
return TextBlockType((_flags >> 8) & 0x0F);
}
int32 flags() const {
return (_flags & 0xFF);
}
const style::color &color() const {
static style::color tmp;
return tmp;//_color;
}
virtual ITextBlock *clone() const = 0;
virtual ~ITextBlock() {
}
protected:
uint16 _from;
uint32 _flags; // 4 bits empty, 16 bits lnkIndex, 4 bits type, 8 bits flags
QFixed _width, _lpadding, _rpadding;
};
class NewlineBlock : public ITextBlock {
public:
Qt::LayoutDirection nextDirection() const {
return _nextDir;
}
ITextBlock *clone() const {
return new NewlineBlock(*this);
}
private:
NewlineBlock(const style::font &font, const QString &str, uint16 from, uint16 length) : ITextBlock(font, str, from, length, 0, st::transparent, 0), _nextDir(Qt::LayoutDirectionAuto) {
_flags |= ((TextBlockTNewline & 0x0F) << 8);
}
Qt::LayoutDirection _nextDir;
friend class Text;
friend class TextParser;
friend class TextPainter;
};
struct TextWord {
TextWord() {
}
TextWord(uint16 from, QFixed width, QFixed rbearing, QFixed rpadding = 0) : from(from),
_rbearing(rbearing.value() > 0x7FFF ? 0x7FFF : (rbearing.value() < -0x7FFF ? -0x7FFF : rbearing.value())), width(width), rpadding(rpadding) {
}
QFixed f_rbearing() const {
return QFixed::fromFixed(_rbearing);
}
uint16 from;
int16 _rbearing;
QFixed width, rpadding;
};
class TextBlock : public ITextBlock {
public:
QFixed f_rbearing() const {
return _words.isEmpty() ? 0 : _words.back().f_rbearing();
}
ITextBlock *clone() const {
return new TextBlock(*this);
}
private:
TextBlock(const style::font &font, const QString &str, QFixed minResizeWidth, uint16 from, uint16 length, uchar flags, const style::color &color, uint16 lnkIndex);
typedef QVector<TextWord> TextWords;
TextWords _words;
friend class Text;
friend class TextParser;
friend class BlockParser;
friend class TextPainter;
};
class EmojiBlock : public ITextBlock {
public:
ITextBlock *clone() const {
return new EmojiBlock(*this);
}
private:
EmojiBlock(const style::font &font, const QString &str, uint16 from, uint16 length, uchar flags, const style::color &color, uint16 lnkIndex, const EmojiData *emoji);
const EmojiData *emoji;
friend class Text;
friend class TextParser;
friend class TextPainter;
};
class SkipBlock : public ITextBlock {
public:
int32 height() const {
return _height;
}
ITextBlock *clone() const {
return new SkipBlock(*this);
}
private:
SkipBlock(const style::font &font, const QString &str, uint16 from, int32 w, int32 h, uint16 lnkIndex);
int32 _height;
friend class Text;
friend class TextParser;
friend class TextPainter;
};

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,85 @@
/*
This file is part of Telegram Desktop,
the official desktop version of Telegram messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
It is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
*/
#pragma once
enum EntityInTextType {
EntityInTextUrl,
EntityInTextCustomUrl,
EntityInTextEmail,
EntityInTextHashtag,
EntityInTextMention,
EntityInTextBotCommand,
EntityInTextBold,
EntityInTextItalic,
EntityInTextCode, // inline
EntityInTextPre, // block
};
struct EntityInText {
EntityInText(EntityInTextType type, int offset, int length, const QString &text = QString()) : type(type), offset(offset), length(length), text(text) {
}
EntityInTextType type;
int offset, length;
QString text;
};
typedef QList<EntityInText> EntitiesInText;
// text preprocess
QString textClean(const QString &text);
QString textRichPrepare(const QString &text);
QString textOneLine(const QString &text, bool trim = true, bool rich = false);
QString textAccentFold(const QString &text);
QString textSearchKey(const QString &text);
bool textSplit(QString &sendingText, EntitiesInText &sendingEntities, QString &leftText, EntitiesInText &leftEntities, int32 limit);
enum {
TextParseMultiline = 0x001,
TextParseLinks = 0x002,
TextParseRichText = 0x004,
TextParseMentions = 0x008,
TextParseHashtags = 0x010,
TextParseBotCommands = 0x020,
TextParseMono = 0x040,
TextTwitterMentions = 0x100,
TextTwitterHashtags = 0x200,
TextInstagramMentions = 0x400,
TextInstagramHashtags = 0x800,
};
EntitiesInText entitiesFromMTP(const QVector<MTPMessageEntity> &entities);
MTPVector<MTPMessageEntity> linksToMTP(const EntitiesInText &links, bool sending = false);
EntitiesInText textParseEntities(QString &text, int32 flags, bool rich = false); // changes text if (flags & TextParseMono)
QString textApplyEntities(const QString &text, const EntitiesInText &entities);
QString prepareTextWithEntities(QString result, EntitiesInText &entities, int32 flags);
inline QString prepareText(QString result, bool checkLinks = false) {
EntitiesInText entities;
return prepareTextWithEntities(result, entities, checkLinks ? (TextParseLinks | TextParseMentions | TextParseHashtags | TextParseBotCommands) : 0);
}
void moveStringPart(QChar *start, int32 &to, int32 &from, int32 count, EntitiesInText &entities);
// replace bad symbols with space and remove \r
void cleanTextWithEntities(QString &result, EntitiesInText &entities);
void trimTextWithEntities(QString &result, EntitiesInText &entities);

View file

@ -22,7 +22,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
#include "ui/toast/toast.h"
#include "ui/twidget.h"
#include "ui/text.h"
#include "ui/text/text.h"
namespace Ui {
namespace Toast {

View file

@ -129,6 +129,9 @@ SOURCES += \
./SourceFiles/mtproto/scheme_auto.cpp \
./SourceFiles/mtproto/session.cpp \
./SourceFiles/ui/buttons/peer_avatar_button.cpp \
./SourceFiles/ui/text/text.cpp \
./SourceFiles/ui/text/text_block.cpp \
./SourceFiles/ui/text/text_entity.cpp \
./SourceFiles/ui/toast/toast.cpp \
./SourceFiles/ui/toast/toast_manager.cpp \
./SourceFiles/ui/toast/toast_widget.cpp \
@ -147,7 +150,6 @@ SOURCES += \
./SourceFiles/ui/images.cpp \
./SourceFiles/ui/scrollarea.cpp \
./SourceFiles/ui/style_core.cpp \
./SourceFiles/ui/text.cpp \
./SourceFiles/ui/twidget.cpp \
./GeneratedFiles/lang_auto.cpp \
./GeneratedFiles/style_auto.cpp \
@ -238,6 +240,9 @@ HEADERS += \
./SourceFiles/mtproto/session.h \
./SourceFiles/pspecific.h \
./SourceFiles/ui/buttons/peer_avatar_button.h \
./SourceFiles/ui/text/text.h \
./SourceFiles/ui/text/text_block.h \
./SourceFiles/ui/text/text_entity.h \
./SourceFiles/ui/toast/toast.h \
./SourceFiles/ui/toast/toast_manager.h \
./SourceFiles/ui/toast/toast_widget.h \
@ -257,7 +262,6 @@ HEADERS += \
./SourceFiles/ui/scrollarea.h \
./SourceFiles/ui/style.h \
./SourceFiles/ui/style_core.h \
./SourceFiles/ui/text.h \
./SourceFiles/ui/twidget.h \
./GeneratedFiles/lang_auto.h \
./GeneratedFiles/style_auto.h \

View file

@ -1170,7 +1170,9 @@
<ClCompile Include="SourceFiles\ui\popupmenu.cpp" />
<ClCompile Include="SourceFiles\ui\scrollarea.cpp" />
<ClCompile Include="SourceFiles\ui\style_core.cpp" />
<ClCompile Include="SourceFiles\ui\text.cpp" />
<ClCompile Include="SourceFiles\ui\text\text.cpp" />
<ClCompile Include="SourceFiles\ui\text\text_block.cpp" />
<ClCompile Include="SourceFiles\ui\text\text_entity.cpp" />
<ClCompile Include="SourceFiles\ui\toast\toast.cpp" />
<ClCompile Include="SourceFiles\ui\toast\toast_manager.cpp" />
<ClCompile Include="SourceFiles\ui\toast\toast_widget.cpp" />
@ -1509,7 +1511,6 @@
</CustomBuild>
<ClInclude Include="SourceFiles\ui\style.h" />
<ClInclude Include="SourceFiles\ui\style_core.h" />
<ClInclude Include="SourceFiles\ui\text.h" />
<CustomBuild Include="SourceFiles\ui\twidget.h">
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Deploy|Win32'">$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs>
<Message Condition="'$(Configuration)|$(Platform)'=='Deploy|Win32'">Moc%27ing twidget.h...</Message>
@ -1524,6 +1525,9 @@
<Outputs Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">.\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp</Outputs>
<Command Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">"$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fstdafx.h" "-f../../SourceFiles/ui/twidget.h" -DAL_LIBTYPE_STATIC -DUNICODE -DWIN32 -DWIN64 -DHAVE_STDINT_H -DZLIB_WINAPI -DQT_NO_DEBUG -DNDEBUG -D_SCL_SECURE_NO_WARNINGS "-I.\..\..\Libraries\lzma\C" "-I.\..\..\Libraries\libexif-0.6.20" "-I.\..\..\Libraries\zlib-1.2.8" "-I.\..\..\Libraries\openssl\Release\include" "-I.\..\..\Libraries\ffmpeg" "-I.\..\..\Libraries\openal-soft\include" "-I.\SourceFiles" "-I.\GeneratedFiles" "-I.\..\..\Libraries\breakpad\src" "-I.\ThirdParty\minizip" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I.\..\..\Libraries\QtStatic\qtbase\include\QtCore\5.5.1\QtCore" "-I.\..\..\Libraries\QtStatic\qtbase\include\QtGui\5.5.1\QtGui"</Command>
</CustomBuild>
<ClInclude Include="SourceFiles\ui\text\text.h" />
<ClInclude Include="SourceFiles\ui\text\text_block.h" />
<ClInclude Include="SourceFiles\ui\text\text_entity.h" />
<ClInclude Include="SourceFiles\ui\toast\toast.h" />
<CustomBuild Include="SourceFiles\ui\toast\toast_manager.h">
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Deploy|Win32'">$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs>

View file

@ -79,6 +79,9 @@
<Filter Include="overview">
<UniqueIdentifier>{ddcc5634-90e7-4815-ba86-a3db539f4774}</UniqueIdentifier>
</Filter>
<Filter Include="ui\text">
<UniqueIdentifier>{850c3d13-024a-4ef3-a6b7-b546e67cca48}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="SourceFiles\main.cpp">
@ -972,9 +975,6 @@
<ClCompile Include="SourceFiles\ui\style_core.cpp">
<Filter>ui</Filter>
</ClCompile>
<ClCompile Include="SourceFiles\ui\text.cpp">
<Filter>ui</Filter>
</ClCompile>
<ClCompile Include="SourceFiles\ui\twidget.cpp">
<Filter>ui</Filter>
</ClCompile>
@ -1068,6 +1068,15 @@
<ClCompile Include="SourceFiles\overview\overview_layout.cpp">
<Filter>overview</Filter>
</ClCompile>
<ClCompile Include="SourceFiles\ui\text\text.cpp">
<Filter>ui\text</Filter>
</ClCompile>
<ClCompile Include="SourceFiles\ui\text\text_entity.cpp">
<Filter>ui\text</Filter>
</ClCompile>
<ClCompile Include="SourceFiles\ui\text\text_block.cpp">
<Filter>ui\text</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="SourceFiles\stdafx.h">
@ -1175,9 +1184,6 @@
<ClInclude Include="SourceFiles\ui\style_core.h">
<Filter>ui</Filter>
</ClInclude>
<ClInclude Include="SourceFiles\ui\text.h">
<Filter>ui</Filter>
</ClInclude>
<ClInclude Include="SourceFiles\ui\toast\toast.h">
<Filter>ui\toast</Filter>
</ClInclude>
@ -1220,6 +1226,15 @@
<ClInclude Include="SourceFiles\overview\overview_layout.h">
<Filter>overview</Filter>
</ClInclude>
<ClInclude Include="SourceFiles\ui\text\text.h">
<Filter>ui\text</Filter>
</ClInclude>
<ClInclude Include="SourceFiles\ui\text\text_entity.h">
<Filter>ui\text</Filter>
</ClInclude>
<ClInclude Include="SourceFiles\ui\text\text_block.h">
<Filter>ui\text</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<CustomBuild Include="SourceFiles\application.h">

View file

@ -457,7 +457,7 @@
111BBEE3D1432C3B517FD539 /* /usr/local/Qt-5.5.1/mkspecs/modules/qt_plugin_qdds.pri */ = {isa = PBXFileReference; lastKnownFileType = text; path = "/usr/local/Qt-5.5.1/mkspecs/modules/qt_plugin_qdds.pri"; sourceTree = "<absolute>"; };
120EBCD9A37DB9A36BFE58C0 /* contactsbox.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = contactsbox.h; path = SourceFiles/boxes/contactsbox.h; sourceTree = "<absolute>"; };
1292B92B4848460640F6A391 /* telegram.qrc */ = {isa = PBXFileReference; lastKnownFileType = text; name = telegram.qrc; path = Resources/telegram.qrc; sourceTree = "<absolute>"; };
135FD3715BFDC50AD7B00E04 /* text.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = text.cpp; path = SourceFiles/ui/text.cpp; sourceTree = "<absolute>"; };
135FD3715BFDC50AD7B00E04 /* text.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = text.cpp; path = SourceFiles/ui/text/text.cpp; sourceTree = "<absolute>"; };
143405635D04698F421A12EA /* aboutbox.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = aboutbox.h; path = SourceFiles/boxes/aboutbox.h; sourceTree = "<absolute>"; };
14437BFDCD58FF1742EF1B35 /* photocropbox.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = photocropbox.h; path = SourceFiles/boxes/photocropbox.h; sourceTree = "<absolute>"; };
152B8D1BCECEB7B0C77E073C /* introwidget.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = introwidget.h; path = SourceFiles/intro/introwidget.h; sourceTree = "<absolute>"; };
@ -570,7 +570,7 @@
6D50D70712776D7ED3B00E5C /* facade.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = facade.cpp; path = SourceFiles/mtproto/facade.cpp; sourceTree = "<absolute>"; };
6E1859D714E4471E053D90C9 /* scrollarea.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = scrollarea.cpp; path = SourceFiles/ui/scrollarea.cpp; sourceTree = "<absolute>"; };
6E67D23B15FC4B628DB2E0B2 /* /usr/local/Qt-5.5.1/mkspecs/qdevice.pri */ = {isa = PBXFileReference; lastKnownFileType = text; path = "/usr/local/Qt-5.5.1/mkspecs/qdevice.pri"; sourceTree = "<absolute>"; };
6E8FD0ED1B60D43929944CD2 /* text.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = text.h; path = SourceFiles/ui/text.h; sourceTree = "<absolute>"; };
6E8FD0ED1B60D43929944CD2 /* text.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = text.h; path = SourceFiles/ui/text/text.h; sourceTree = "<absolute>"; };
710C982FC773400941B3AFBC /* dropdown.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = dropdown.cpp; path = SourceFiles/dropdown.cpp; sourceTree = "<absolute>"; };
723F90793B2C195E2CCB2233 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = /System/Library/Frameworks/AppKit.framework; sourceTree = "<absolute>"; };
73737DC91E390C4AB18FB595 /* pspecific_mac_p.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = pspecific_mac_p.mm; path = SourceFiles/pspecific_mac_p.mm; sourceTree = "<absolute>"; };