mirror of
https://github.com/vale981/tdesktop
synced 2025-03-06 10:11:41 -05:00
Merge branch 'dev' of https://github.com/telegramdesktop/tdesktop into dev
This commit is contained in:
commit
971ec71836
23 changed files with 2791 additions and 2579 deletions
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,29 +3257,43 @@ 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()) {
|
||||
result = qMax(result, st::normalFont->width(formatPlayedText(song->duration, song->duration)));
|
||||
result = qMax(result, st::normalFont->width(formatDurationAndSizeText(song->duration, document->size)));
|
||||
} else if (VoiceData *voice = document->voice()) {
|
||||
result = qMax(result, st::normalFont->width(formatPlayedText(voice->duration, voice->duration)));
|
||||
result = qMax(result, st::normalFont->width(formatDurationAndSizeText(voice->duration, document->size)));
|
||||
} else if (document->isVideo()) {
|
||||
result = qMax(result, st::normalFont->width(formatDurationAndSizeText(document->duration(), document->size)));
|
||||
} else {
|
||||
result = qMax(result, st::normalFont->width(formatSizeText(document->size)));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
int32 gifMaxStatusWidth(DocumentData *document) {
|
||||
int32 result = st::normalFont->width(formatDownloadText(document->size, document->size));
|
||||
result = qMax(result, st::normalFont->width(formatGifAndSizeText(document->size)));
|
||||
return result;
|
||||
int32 documentMaxStatusWidth(DocumentData *document) {
|
||||
int32 result = st::normalFont->width(formatDownloadText(document->size, document->size));
|
||||
if (SongData *song = document->song()) {
|
||||
result = qMax(result, st::normalFont->width(formatPlayedText(song->duration, song->duration)));
|
||||
result = qMax(result, st::normalFont->width(formatDurationAndSizeText(song->duration, document->size)));
|
||||
} else if (VoiceData *voice = document->voice()) {
|
||||
result = qMax(result, st::normalFont->width(formatPlayedText(voice->duration, voice->duration)));
|
||||
result = qMax(result, st::normalFont->width(formatDurationAndSizeText(voice->duration, document->size)));
|
||||
} else if (document->isVideo()) {
|
||||
result = qMax(result, st::normalFont->width(formatDurationAndSizeText(document->duration(), document->size)));
|
||||
} else {
|
||||
result = qMax(result, st::normalFont->width(formatSizeText(document->size)));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
int32 gifMaxStatusWidth(DocumentData *document) {
|
||||
int32 result = st::normalFont->width(formatDownloadText(document->size, document->size));
|
||||
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) {
|
||||
if (active && !dataLoaded()) {
|
||||
|
@ -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,12 +5928,22 @@ void HistoryWebPage::detachFromParent() {
|
|||
if (_attach) _attach->detachFromParent();
|
||||
}
|
||||
|
||||
const QString HistoryWebPage::inDialogsText() const {
|
||||
QString HistoryWebPage::inDialogsText() const {
|
||||
return QString();
|
||||
}
|
||||
|
||||
const QString HistoryWebPage::inHistoryText() const {
|
||||
return QString();
|
||||
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() {
|
||||
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
*/
|
||||
#pragma once
|
||||
|
||||
#include "text.h"
|
||||
#include "ui/text/text.h"
|
||||
|
||||
class PopupMenu : public TWidget {
|
||||
Q_OBJECT
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -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);
|
322
Telegram/SourceFiles/ui/text/text_block.cpp
Normal file
322
Telegram/SourceFiles/ui/text/text_block.cpp
Normal 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 ¤t, 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 ¤t = 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(¤t);
|
||||
QFontEngine *fontEngine = eng->fontEngine(current);
|
||||
if (lbh.fontEngine != fontEngine) {
|
||||
lbh.fontEngine = fontEngine;
|
||||
}
|
||||
}
|
||||
const QScriptItem ¤t = 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;
|
||||
}
|
214
Telegram/SourceFiles/ui/text/text_block.h
Normal file
214
Telegram/SourceFiles/ui/text/text_block.h
Normal 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;
|
||||
};
|
1919
Telegram/SourceFiles/ui/text/text_entity.cpp
Normal file
1919
Telegram/SourceFiles/ui/text/text_entity.cpp
Normal file
File diff suppressed because it is too large
Load diff
85
Telegram/SourceFiles/ui/text/text_entity.h
Normal file
85
Telegram/SourceFiles/ui/text/text_entity.h
Normal 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);
|
|
@ -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 {
|
||||
|
|
|
@ -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 \
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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>"; };
|
||||
|
|
Loading…
Add table
Reference in a new issue