diff --git a/Telegram/SourceFiles/boxes/contactsbox.cpp b/Telegram/SourceFiles/boxes/contactsbox.cpp index 326c94bef..92540758a 100644 --- a/Telegram/SourceFiles/boxes/contactsbox.cpp +++ b/Telegram/SourceFiles/boxes/contactsbox.cpp @@ -957,7 +957,6 @@ void ContactsInner::peopleReceived(const QString &query, const QVector if (p->asUser()->botInfo->cantJoinGroups) continue; } if (_channel) { - if (_channel->isMegagroup() && _membersFilter == MembersFilterAdmins) continue; if (!_channel->isMegagroup() && _membersFilter != MembersFilterAdmins) continue; } } diff --git a/Telegram/SourceFiles/dialogs/dialogs_row.h b/Telegram/SourceFiles/dialogs/dialogs_row.h index 88dd3ca6f..45180f4ff 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_row.h +++ b/Telegram/SourceFiles/dialogs/dialogs_row.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" class History; class HistoryItem; diff --git a/Telegram/SourceFiles/history.cpp b/Telegram/SourceFiles/history.cpp index 930d7c65e..ec27218e1 100644 --- a/Telegram/SourceFiles/history.cpp +++ b/Telegram/SourceFiles/history.cpp @@ -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(); - 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()) { 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()) { + caption = &captioned->_caption; + } + QString attachType = lang(lng_in_dlg_file); if (Has()) { - 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()) { if (!named->_name.isEmpty()) { - result.append(qsl(" : ")).append(named->_name); + attachType.append(qstr(" : ")).append(named->_name); } } - if (auto captioned = Get()) { - 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()) { if (selection == FullSelection) { diff --git a/Telegram/SourceFiles/history.h b/Telegram/SourceFiles/history.h index d60c855f6..68acef5d3 100644 --- a/Telegram/SourceFiles/history.h +++ b/Telegram/SourceFiles/history.h @@ -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; diff --git a/Telegram/SourceFiles/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp index 2c3aa309d..c5556bb25 100644 --- a/Telegram/SourceFiles/historywidget.cpp +++ b/Telegram/SourceFiles/historywidget.cpp @@ -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); } } diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.h b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.h index e61c975e3..e36bcc3c5 100644 --- a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.h +++ b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.h @@ -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 { diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_layout_item.h b/Telegram/SourceFiles/inline_bots/inline_bot_layout_item.h index 0c091845b..ddd606f62 100644 --- a/Telegram/SourceFiles/inline_bots/inline_bot_layout_item.h +++ b/Telegram/SourceFiles/inline_bots/inline_bot_layout_item.h @@ -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; diff --git a/Telegram/SourceFiles/intro/introwidget.cpp b/Telegram/SourceFiles/intro/introwidget.cpp index d79f80e0c..5ec5c9ad6 100644 --- a/Telegram/SourceFiles/intro/introwidget.cpp +++ b/Telegram/SourceFiles/intro/introwidget.cpp @@ -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; diff --git a/Telegram/SourceFiles/passcodewidget.cpp b/Telegram/SourceFiles/passcodewidget.cpp index 95aa4871d..b8e932f0f 100644 --- a/Telegram/SourceFiles/passcodewidget.cpp +++ b/Telegram/SourceFiles/passcodewidget.cpp @@ -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)) diff --git a/Telegram/SourceFiles/stdafx.h b/Telegram/SourceFiles/stdafx.h index db0521fb8..d8affc31f 100644 --- a/Telegram/SourceFiles/stdafx.h +++ b/Telegram/SourceFiles/stdafx.h @@ -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" diff --git a/Telegram/SourceFiles/ui/emoji_config.h b/Telegram/SourceFiles/ui/emoji_config.h index 90802596f..f7e6c2597 100644 --- a/Telegram/SourceFiles/ui/emoji_config.h +++ b/Telegram/SourceFiles/ui/emoji_config.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); diff --git a/Telegram/SourceFiles/ui/popupmenu.h b/Telegram/SourceFiles/ui/popupmenu.h index 64ff31fb6..ccc1e8cfb 100644 --- a/Telegram/SourceFiles/ui/popupmenu.h +++ b/Telegram/SourceFiles/ui/popupmenu.h @@ -17,7 +17,7 @@ */ #pragma once -#include "text.h" +#include "ui/text/text.h" class PopupMenu : public TWidget { Q_OBJECT diff --git a/Telegram/SourceFiles/ui/text.cpp b/Telegram/SourceFiles/ui/text/text.cpp similarity index 58% rename from Telegram/SourceFiles/ui/text.cpp rename to Telegram/SourceFiles/ui/text/text.cpp index b47ae3388..ff9915df3 100644 --- a/Telegram/SourceFiles/ui/text.cpp +++ b/Telegram/SourceFiles/ui/text/text.cpp @@ -19,11 +19,12 @@ Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org */ #include "stdafx.h" -#include "text.h" +#include "ui/text/text.h" #include #include "core/click_handler_types.h" +#include "ui/text/text_block.h" #include "lang.h" #include "pspecific.h" #include "boxes/confirmbox.h" @@ -31,51 +32,21 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org namespace { - const QRegularExpression _reDomain(QString::fromUtf8("(?|'\"\\[\\]\\{\\}`\\~\\!\\%\\^\\*\\(\\)\\-\\+=\\x10])#[\\w]{2,64}([\\W]|$)"), QRegularExpression::UseUnicodePropertiesOption); - const QRegularExpression _reMention(qsl("(^|[\\s\\.,:;<>|'\"\\[\\]\\{\\}`\\~\\!\\%\\^\\*\\(\\)\\-\\+=\\x10])@[A-Za-z_0-9]{1,32}([\\W]|$)"), QRegularExpression::UseUnicodePropertiesOption); - const QRegularExpression _reBotCommand(qsl("(^|[\\s\\.,:;<>|'\"\\[\\]\\{\\}`\\~\\!\\%\\^\\*\\(\\)\\-\\+=\\x10])/[A-Za-z_0-9]{1,64}(@[A-Za-z_0-9]{5,32})?([\\W]|$)")); - const QRegularExpression _rePre(qsl("(^|[\\s\\.,:;<>|'\"\\[\\]\\{\\}`\\~\\!\\?\\%\\^\\*\\(\\)\\-\\+=\\x10])(````?)[\\s\\S]+?(````?)([\\s\\.,:;<>|'\"\\[\\]\\{\\}`\\~\\!\\?\\%\\^\\*\\(\\)\\-\\+=\\x10]|$)"), QRegularExpression::UseUnicodePropertiesOption); - const QRegularExpression _reCode(qsl("(^|[\\s\\.,:;<>|'\"\\[\\]\\{\\}`\\~\\!\\?\\%\\^\\*\\(\\)\\-\\+=\\x10])(`)[^\\n]+?(`)([\\s\\.,:;<>|'\"\\[\\]\\{\\}`\\~\\!\\?\\%\\^\\*\\(\\)\\-\\+=\\x10]|$)"), QRegularExpression::UseUnicodePropertiesOption); - QSet _validProtocols, _validTopDomains; +const style::textStyle *_textStyle = nullptr; - const style::textStyle *_textStyle = 0; - - void _initDefault() { - _textStyle = &st::defaultTextStyle; - } - - inline int32 _blockHeight(const ITextBlock *b, const style::font &font) { - return (b->type() == TextBlockTSkip) ? static_cast(b)->height() : (_textStyle->lineHeight > font->height) ? _textStyle->lineHeight : font->height; - } - - inline QFixed _blockRBearing(const ITextBlock *b) { - return (b->type() == TextBlockTText) ? static_cast(b)->f_rbearing() : 0; - } +void _initDefault() { + _textStyle = &st::defaultTextStyle; } -const QRegularExpression &reDomain() { - return _reDomain; +inline int32 _blockHeight(const ITextBlock *b, const style::font &font) { + return (b->type() == TextBlockTSkip) ? static_cast(b)->height() : (_textStyle->lineHeight > font->height) ? _textStyle->lineHeight : font->height; } -const QRegularExpression &reMailName() { - return _reMailName; +inline QFixed _blockRBearing(const ITextBlock *b) { + return (b->type() == TextBlockTText) ? static_cast(b)->f_rbearing() : 0; } -const QRegularExpression &reMailStart() { - return _reMailStart; -} - -const QRegularExpression &reHashtag() { - return _reHashtag; -} - -const QRegularExpression &reBotCommand() { - return _reBotCommand; -} +} // namespace const style::textStyle *textstyleCurrent() { return _textStyle; @@ -85,60 +56,6 @@ void textstyleSet(const style::textStyle *style) { _textStyle = style ? style : &st::defaultTextStyle; } -QString textOneLine(const QString &text, bool trim, bool rich) { - QString result(text); - const QChar *s = text.unicode(), *ch = s, *e = text.unicode() + text.size(); - if (trim) { - while (s < e && chIsTrimmed(*s)) { - ++s; - } - while (s < e && chIsTrimmed(*(e - 1))) { - --e; - } - if (e - s != text.size()) { - result = text.mid(s - text.unicode(), e - s); - } - } - for (const QChar *ch = s; ch != e; ++ch) { - if (chIsNewline(*ch)) { - result[int(ch - s)] = QChar::Space; - } - } - return result; -} - -QString textClean(const QString &text) { - QString result(text); - for (const QChar *s = text.unicode(), *ch = s, *e = text.unicode() + text.size(); ch != e; ++ch) { - if (*ch == TextCommand) { - result[int(ch - s)] = QChar::Space; - } - } - return result; -} - -QString textRichPrepare(const QString &text) { - QString result; - result.reserve(text.size()); - const QChar *s = text.constData(), *ch = s; - for (const QChar *e = s + text.size(); ch != e; ++ch) { - if (*ch == TextCommand) { - if (ch > s) result.append(s, ch - s); - result.append(QChar::Space); - s = ch + 1; - continue; - } - if (ch->unicode() == '\\' || ch->unicode() == '[') { - if (ch > s) result.append(s, ch - s); - result.append('\\'); - s = ch; - continue; - } - } - if (ch > s) result.append(s, ch - s); - return result; -} - QString textcmdSkipBlock(ushort w, ushort h) { static QString cmd(5, TextCommand); cmd[1] = QChar(TextCommandSkipBlock); @@ -339,7 +256,7 @@ public: } else if (!original.isEmpty() && original.at(0) == '#') { result = original; fullDisplayed = -2; // hashtag - } else if (_reMailStart.match(original).hasMatch()) { + } else if (reMailStart().match(original).hasMatch()) { result = original; fullDisplayed = -1; // email } else { @@ -2695,6 +2612,10 @@ bool Text::hasLinks() const { return !_links.isEmpty(); } +bool Text::hasSkipBlock() const { + return _blocks.isEmpty() ? false : _blocks.back()->type() == TextBlockTSkip; +} + void Text::setSkipBlock(int32 width, int32 height) { if (!_blocks.isEmpty() && _blocks.back()->type() == TextBlockTSkip) { SkipBlock *block = static_cast(_blocks.back()); @@ -3000,6 +2921,10 @@ TextSelection Text::adjustSelection(TextSelection selection, TextSelectType sele } QString Text::original(TextSelection selection, ExpandLinksMode mode) const { + if (isEmpty() || selection.empty()) { + return QString(); + } + QString result, emptyurl; result.reserve(_text.size()); @@ -3154,1980 +3079,6 @@ void Text::clearFields() { _startDir = Qt::LayoutDirectionAuto; } -// 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() % 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; -} - -namespace { - void regOneProtocol(const QString &protocol) { - _validProtocols.insert(hashCrc32(protocol.constData(), protocol.size() * sizeof(QChar))); - } - void regOneTopDomain(const QString &domain) { - _validTopDomains.insert(hashCrc32(domain.constData(), domain.size() * sizeof(QChar))); - } -} - -const QSet &validProtocols() { - return _validProtocols; -} -const QSet &validTopDomains() { - return _validTopDomains; -} - -void initLinkSets() { - if (!_validProtocols.isEmpty() || !_validTopDomains.isEmpty()) return; - - regOneProtocol(qsl("itmss")); // itunes - regOneProtocol(qsl("http")); - regOneProtocol(qsl("https")); - regOneProtocol(qsl("ftp")); - regOneProtocol(qsl("tg")); // local urls - - regOneTopDomain(qsl("ac")); - regOneTopDomain(qsl("ad")); - regOneTopDomain(qsl("ae")); - regOneTopDomain(qsl("af")); - regOneTopDomain(qsl("ag")); - regOneTopDomain(qsl("ai")); - regOneTopDomain(qsl("al")); - regOneTopDomain(qsl("am")); - regOneTopDomain(qsl("an")); - regOneTopDomain(qsl("ao")); - regOneTopDomain(qsl("aq")); - regOneTopDomain(qsl("ar")); - regOneTopDomain(qsl("as")); - regOneTopDomain(qsl("at")); - regOneTopDomain(qsl("au")); - regOneTopDomain(qsl("aw")); - regOneTopDomain(qsl("ax")); - regOneTopDomain(qsl("az")); - regOneTopDomain(qsl("ba")); - regOneTopDomain(qsl("bb")); - regOneTopDomain(qsl("bd")); - regOneTopDomain(qsl("be")); - regOneTopDomain(qsl("bf")); - regOneTopDomain(qsl("bg")); - regOneTopDomain(qsl("bh")); - regOneTopDomain(qsl("bi")); - regOneTopDomain(qsl("bj")); - regOneTopDomain(qsl("bm")); - regOneTopDomain(qsl("bn")); - regOneTopDomain(qsl("bo")); - regOneTopDomain(qsl("br")); - regOneTopDomain(qsl("bs")); - regOneTopDomain(qsl("bt")); - regOneTopDomain(qsl("bv")); - regOneTopDomain(qsl("bw")); - regOneTopDomain(qsl("by")); - regOneTopDomain(qsl("bz")); - regOneTopDomain(qsl("ca")); - regOneTopDomain(qsl("cc")); - regOneTopDomain(qsl("cd")); - regOneTopDomain(qsl("cf")); - regOneTopDomain(qsl("cg")); - regOneTopDomain(qsl("ch")); - regOneTopDomain(qsl("ci")); - regOneTopDomain(qsl("ck")); - regOneTopDomain(qsl("cl")); - regOneTopDomain(qsl("cm")); - regOneTopDomain(qsl("cn")); - regOneTopDomain(qsl("co")); - regOneTopDomain(qsl("cr")); - regOneTopDomain(qsl("cu")); - regOneTopDomain(qsl("cv")); - regOneTopDomain(qsl("cx")); - regOneTopDomain(qsl("cy")); - regOneTopDomain(qsl("cz")); - regOneTopDomain(qsl("de")); - regOneTopDomain(qsl("dj")); - regOneTopDomain(qsl("dk")); - regOneTopDomain(qsl("dm")); - regOneTopDomain(qsl("do")); - regOneTopDomain(qsl("dz")); - regOneTopDomain(qsl("ec")); - regOneTopDomain(qsl("ee")); - regOneTopDomain(qsl("eg")); - regOneTopDomain(qsl("eh")); - regOneTopDomain(qsl("er")); - regOneTopDomain(qsl("es")); - regOneTopDomain(qsl("et")); - regOneTopDomain(qsl("eu")); - regOneTopDomain(qsl("fi")); - regOneTopDomain(qsl("fj")); - regOneTopDomain(qsl("fk")); - regOneTopDomain(qsl("fm")); - regOneTopDomain(qsl("fo")); - regOneTopDomain(qsl("fr")); - regOneTopDomain(qsl("ga")); - regOneTopDomain(qsl("gd")); - regOneTopDomain(qsl("ge")); - regOneTopDomain(qsl("gf")); - regOneTopDomain(qsl("gg")); - regOneTopDomain(qsl("gh")); - regOneTopDomain(qsl("gi")); - regOneTopDomain(qsl("gl")); - regOneTopDomain(qsl("gm")); - regOneTopDomain(qsl("gn")); - regOneTopDomain(qsl("gp")); - regOneTopDomain(qsl("gq")); - regOneTopDomain(qsl("gr")); - regOneTopDomain(qsl("gs")); - regOneTopDomain(qsl("gt")); - regOneTopDomain(qsl("gu")); - regOneTopDomain(qsl("gw")); - regOneTopDomain(qsl("gy")); - regOneTopDomain(qsl("hk")); - regOneTopDomain(qsl("hm")); - regOneTopDomain(qsl("hn")); - regOneTopDomain(qsl("hr")); - regOneTopDomain(qsl("ht")); - regOneTopDomain(qsl("hu")); - regOneTopDomain(qsl("id")); - regOneTopDomain(qsl("ie")); - regOneTopDomain(qsl("il")); - regOneTopDomain(qsl("im")); - regOneTopDomain(qsl("in")); - regOneTopDomain(qsl("io")); - regOneTopDomain(qsl("iq")); - regOneTopDomain(qsl("ir")); - regOneTopDomain(qsl("is")); - regOneTopDomain(qsl("it")); - regOneTopDomain(qsl("je")); - regOneTopDomain(qsl("jm")); - regOneTopDomain(qsl("jo")); - regOneTopDomain(qsl("jp")); - regOneTopDomain(qsl("ke")); - regOneTopDomain(qsl("kg")); - regOneTopDomain(qsl("kh")); - regOneTopDomain(qsl("ki")); - regOneTopDomain(qsl("km")); - regOneTopDomain(qsl("kn")); - regOneTopDomain(qsl("kp")); - regOneTopDomain(qsl("kr")); - regOneTopDomain(qsl("kw")); - regOneTopDomain(qsl("ky")); - regOneTopDomain(qsl("kz")); - regOneTopDomain(qsl("la")); - regOneTopDomain(qsl("lb")); - regOneTopDomain(qsl("lc")); - regOneTopDomain(qsl("li")); - regOneTopDomain(qsl("lk")); - regOneTopDomain(qsl("lr")); - regOneTopDomain(qsl("ls")); - regOneTopDomain(qsl("lt")); - regOneTopDomain(qsl("lu")); - regOneTopDomain(qsl("lv")); - regOneTopDomain(qsl("ly")); - regOneTopDomain(qsl("ma")); - regOneTopDomain(qsl("mc")); - regOneTopDomain(qsl("md")); - regOneTopDomain(qsl("me")); - regOneTopDomain(qsl("mg")); - regOneTopDomain(qsl("mh")); - regOneTopDomain(qsl("mk")); - regOneTopDomain(qsl("ml")); - regOneTopDomain(qsl("mm")); - regOneTopDomain(qsl("mn")); - regOneTopDomain(qsl("mo")); - regOneTopDomain(qsl("mp")); - regOneTopDomain(qsl("mq")); - regOneTopDomain(qsl("mr")); - regOneTopDomain(qsl("ms")); - regOneTopDomain(qsl("mt")); - regOneTopDomain(qsl("mu")); - regOneTopDomain(qsl("mv")); - regOneTopDomain(qsl("mw")); - regOneTopDomain(qsl("mx")); - regOneTopDomain(qsl("my")); - regOneTopDomain(qsl("mz")); - regOneTopDomain(qsl("na")); - regOneTopDomain(qsl("nc")); - regOneTopDomain(qsl("ne")); - regOneTopDomain(qsl("nf")); - regOneTopDomain(qsl("ng")); - regOneTopDomain(qsl("ni")); - regOneTopDomain(qsl("nl")); - regOneTopDomain(qsl("no")); - regOneTopDomain(qsl("np")); - regOneTopDomain(qsl("nr")); - regOneTopDomain(qsl("nu")); - regOneTopDomain(qsl("nz")); - regOneTopDomain(qsl("om")); - regOneTopDomain(qsl("pa")); - regOneTopDomain(qsl("pe")); - regOneTopDomain(qsl("pf")); - regOneTopDomain(qsl("pg")); - regOneTopDomain(qsl("ph")); - regOneTopDomain(qsl("pk")); - regOneTopDomain(qsl("pl")); - regOneTopDomain(qsl("pm")); - regOneTopDomain(qsl("pn")); - regOneTopDomain(qsl("pr")); - regOneTopDomain(qsl("ps")); - regOneTopDomain(qsl("pt")); - regOneTopDomain(qsl("pw")); - regOneTopDomain(qsl("py")); - regOneTopDomain(qsl("qa")); - regOneTopDomain(qsl("re")); - regOneTopDomain(qsl("ro")); - regOneTopDomain(qsl("ru")); - regOneTopDomain(qsl("rs")); - regOneTopDomain(qsl("rw")); - regOneTopDomain(qsl("sa")); - regOneTopDomain(qsl("sb")); - regOneTopDomain(qsl("sc")); - regOneTopDomain(qsl("sd")); - regOneTopDomain(qsl("se")); - regOneTopDomain(qsl("sg")); - regOneTopDomain(qsl("sh")); - regOneTopDomain(qsl("si")); - regOneTopDomain(qsl("sj")); - regOneTopDomain(qsl("sk")); - regOneTopDomain(qsl("sl")); - regOneTopDomain(qsl("sm")); - regOneTopDomain(qsl("sn")); - regOneTopDomain(qsl("so")); - regOneTopDomain(qsl("sr")); - regOneTopDomain(qsl("ss")); - regOneTopDomain(qsl("st")); - regOneTopDomain(qsl("su")); - regOneTopDomain(qsl("sv")); - regOneTopDomain(qsl("sx")); - regOneTopDomain(qsl("sy")); - regOneTopDomain(qsl("sz")); - regOneTopDomain(qsl("tc")); - regOneTopDomain(qsl("td")); - regOneTopDomain(qsl("tf")); - regOneTopDomain(qsl("tg")); - regOneTopDomain(qsl("th")); - regOneTopDomain(qsl("tj")); - regOneTopDomain(qsl("tk")); - regOneTopDomain(qsl("tl")); - regOneTopDomain(qsl("tm")); - regOneTopDomain(qsl("tn")); - regOneTopDomain(qsl("to")); - regOneTopDomain(qsl("tp")); - regOneTopDomain(qsl("tr")); - regOneTopDomain(qsl("tt")); - regOneTopDomain(qsl("tv")); - regOneTopDomain(qsl("tw")); - regOneTopDomain(qsl("tz")); - regOneTopDomain(qsl("ua")); - regOneTopDomain(qsl("ug")); - regOneTopDomain(qsl("uk")); - regOneTopDomain(qsl("um")); - regOneTopDomain(qsl("us")); - regOneTopDomain(qsl("uy")); - regOneTopDomain(qsl("uz")); - regOneTopDomain(qsl("va")); - regOneTopDomain(qsl("vc")); - regOneTopDomain(qsl("ve")); - regOneTopDomain(qsl("vg")); - regOneTopDomain(qsl("vi")); - regOneTopDomain(qsl("vn")); - regOneTopDomain(qsl("vu")); - regOneTopDomain(qsl("wf")); - regOneTopDomain(qsl("ws")); - regOneTopDomain(qsl("ye")); - regOneTopDomain(qsl("yt")); - regOneTopDomain(qsl("yu")); - regOneTopDomain(qsl("za")); - regOneTopDomain(qsl("zm")); - regOneTopDomain(qsl("zw")); - regOneTopDomain(qsl("arpa")); - regOneTopDomain(qsl("aero")); - regOneTopDomain(qsl("asia")); - regOneTopDomain(qsl("biz")); - regOneTopDomain(qsl("cat")); - regOneTopDomain(qsl("com")); - regOneTopDomain(qsl("coop")); - regOneTopDomain(qsl("info")); - regOneTopDomain(qsl("int")); - regOneTopDomain(qsl("jobs")); - regOneTopDomain(qsl("mobi")); - regOneTopDomain(qsl("museum")); - regOneTopDomain(qsl("name")); - regOneTopDomain(qsl("net")); - regOneTopDomain(qsl("org")); - regOneTopDomain(qsl("post")); - regOneTopDomain(qsl("pro")); - regOneTopDomain(qsl("tel")); - regOneTopDomain(qsl("travel")); - regOneTopDomain(qsl("xxx")); - regOneTopDomain(qsl("edu")); - regOneTopDomain(qsl("gov")); - regOneTopDomain(qsl("mil")); - regOneTopDomain(qsl("local")); - regOneTopDomain(qsl("xn--lgbbat1ad8j")); - regOneTopDomain(qsl("xn--54b7fta0cc")); - regOneTopDomain(qsl("xn--fiqs8s")); - regOneTopDomain(qsl("xn--fiqz9s")); - regOneTopDomain(qsl("xn--wgbh1c")); - regOneTopDomain(qsl("xn--node")); - regOneTopDomain(qsl("xn--j6w193g")); - regOneTopDomain(qsl("xn--h2brj9c")); - regOneTopDomain(qsl("xn--mgbbh1a71e")); - regOneTopDomain(qsl("xn--fpcrj9c3d")); - regOneTopDomain(qsl("xn--gecrj9c")); - regOneTopDomain(qsl("xn--s9brj9c")); - regOneTopDomain(qsl("xn--xkc2dl3a5ee0h")); - regOneTopDomain(qsl("xn--45brj9c")); - regOneTopDomain(qsl("xn--mgba3a4f16a")); - regOneTopDomain(qsl("xn--mgbayh7gpa")); - regOneTopDomain(qsl("xn--80ao21a")); - regOneTopDomain(qsl("xn--mgbx4cd0ab")); - regOneTopDomain(qsl("xn--l1acc")); - regOneTopDomain(qsl("xn--mgbc0a9azcg")); - regOneTopDomain(qsl("xn--mgb9awbf")); - regOneTopDomain(qsl("xn--mgbai9azgqp6j")); - regOneTopDomain(qsl("xn--ygbi2ammx")); - regOneTopDomain(qsl("xn--wgbl6a")); - regOneTopDomain(qsl("xn--p1ai")); - regOneTopDomain(qsl("xn--mgberp4a5d4ar")); - regOneTopDomain(qsl("xn--90a3ac")); - regOneTopDomain(qsl("xn--yfro4i67o")); - regOneTopDomain(qsl("xn--clchc0ea0b2g2a9gcd")); - regOneTopDomain(qsl("xn--3e0b707e")); - regOneTopDomain(qsl("xn--fzc2c9e2c")); - regOneTopDomain(qsl("xn--xkc2al3hye2a")); - regOneTopDomain(qsl("xn--mgbtf8fl")); - regOneTopDomain(qsl("xn--kprw13d")); - regOneTopDomain(qsl("xn--kpry57d")); - regOneTopDomain(qsl("xn--o3cw4h")); - regOneTopDomain(qsl("xn--pgbs0dh")); - regOneTopDomain(qsl("xn--j1amh")); - regOneTopDomain(qsl("xn--mgbaam7a8h")); - regOneTopDomain(qsl("xn--mgb2ddes")); - regOneTopDomain(qsl("xn--ogbpf8fl")); - regOneTopDomain(QString::fromUtf8("рф")); -} - -namespace { - // accent char list taken from https://github.com/aristus/accent-folding - inline QChar chNoAccent(int32 code) { - switch (code) { - case 7834: return QChar(97); - case 193: return QChar(97); - case 225: return QChar(97); - case 192: return QChar(97); - case 224: return QChar(97); - case 258: return QChar(97); - case 259: return QChar(97); - case 7854: return QChar(97); - case 7855: return QChar(97); - case 7856: return QChar(97); - case 7857: return QChar(97); - case 7860: return QChar(97); - case 7861: return QChar(97); - case 7858: return QChar(97); - case 7859: return QChar(97); - case 194: return QChar(97); - case 226: return QChar(97); - case 7844: return QChar(97); - case 7845: return QChar(97); - case 7846: return QChar(97); - case 7847: return QChar(97); - case 7850: return QChar(97); - case 7851: return QChar(97); - case 7848: return QChar(97); - case 7849: return QChar(97); - case 461: return QChar(97); - case 462: return QChar(97); - case 197: return QChar(97); - case 229: return QChar(97); - case 506: return QChar(97); - case 507: return QChar(97); - case 196: return QChar(97); - case 228: return QChar(97); - case 478: return QChar(97); - case 479: return QChar(97); - case 195: return QChar(97); - case 227: return QChar(97); - case 550: return QChar(97); - case 551: return QChar(97); - case 480: return QChar(97); - case 481: return QChar(97); - case 260: return QChar(97); - case 261: return QChar(97); - case 256: return QChar(97); - case 257: return QChar(97); - case 7842: return QChar(97); - case 7843: return QChar(97); - case 512: return QChar(97); - case 513: return QChar(97); - case 514: return QChar(97); - case 515: return QChar(97); - case 7840: return QChar(97); - case 7841: return QChar(97); - case 7862: return QChar(97); - case 7863: return QChar(97); - case 7852: return QChar(97); - case 7853: return QChar(97); - case 7680: return QChar(97); - case 7681: return QChar(97); - case 570: return QChar(97); - case 11365: return QChar(97); - case 508: return QChar(97); - case 509: return QChar(97); - case 482: return QChar(97); - case 483: return QChar(97); - case 7682: return QChar(98); - case 7683: return QChar(98); - case 7684: return QChar(98); - case 7685: return QChar(98); - case 7686: return QChar(98); - case 7687: return QChar(98); - case 579: return QChar(98); - case 384: return QChar(98); - case 7532: return QChar(98); - case 385: return QChar(98); - case 595: return QChar(98); - case 386: return QChar(98); - case 387: return QChar(98); - case 262: return QChar(99); - case 263: return QChar(99); - case 264: return QChar(99); - case 265: return QChar(99); - case 268: return QChar(99); - case 269: return QChar(99); - case 266: return QChar(99); - case 267: return QChar(99); - case 199: return QChar(99); - case 231: return QChar(99); - case 7688: return QChar(99); - case 7689: return QChar(99); - case 571: return QChar(99); - case 572: return QChar(99); - case 391: return QChar(99); - case 392: return QChar(99); - case 597: return QChar(99); - case 270: return QChar(100); - case 271: return QChar(100); - case 7690: return QChar(100); - case 7691: return QChar(100); - case 7696: return QChar(100); - case 7697: return QChar(100); - case 7692: return QChar(100); - case 7693: return QChar(100); - case 7698: return QChar(100); - case 7699: return QChar(100); - case 7694: return QChar(100); - case 7695: return QChar(100); - case 272: return QChar(100); - case 273: return QChar(100); - case 7533: return QChar(100); - case 393: return QChar(100); - case 598: return QChar(100); - case 394: return QChar(100); - case 599: return QChar(100); - case 395: return QChar(100); - case 396: return QChar(100); - case 545: return QChar(100); - case 240: return QChar(100); - case 201: return QChar(101); - case 399: return QChar(101); - case 398: return QChar(101); - case 477: return QChar(101); - case 233: return QChar(101); - case 200: return QChar(101); - case 232: return QChar(101); - case 276: return QChar(101); - case 277: return QChar(101); - case 202: return QChar(101); - case 234: return QChar(101); - case 7870: return QChar(101); - case 7871: return QChar(101); - case 7872: return QChar(101); - case 7873: return QChar(101); - case 7876: return QChar(101); - case 7877: return QChar(101); - case 7874: return QChar(101); - case 7875: return QChar(101); - case 282: return QChar(101); - case 283: return QChar(101); - case 203: return QChar(101); - case 235: return QChar(101); - case 7868: return QChar(101); - case 7869: return QChar(101); - case 278: return QChar(101); - case 279: return QChar(101); - case 552: return QChar(101); - case 553: return QChar(101); - case 7708: return QChar(101); - case 7709: return QChar(101); - case 280: return QChar(101); - case 281: return QChar(101); - case 274: return QChar(101); - case 275: return QChar(101); - case 7702: return QChar(101); - case 7703: return QChar(101); - case 7700: return QChar(101); - case 7701: return QChar(101); - case 7866: return QChar(101); - case 7867: return QChar(101); - case 516: return QChar(101); - case 517: return QChar(101); - case 518: return QChar(101); - case 519: return QChar(101); - case 7864: return QChar(101); - case 7865: return QChar(101); - case 7878: return QChar(101); - case 7879: return QChar(101); - case 7704: return QChar(101); - case 7705: return QChar(101); - case 7706: return QChar(101); - case 7707: return QChar(101); - case 582: return QChar(101); - case 583: return QChar(101); - case 602: return QChar(101); - case 605: return QChar(101); - case 7710: return QChar(102); - case 7711: return QChar(102); - case 7534: return QChar(102); - case 401: return QChar(102); - case 402: return QChar(102); - case 500: return QChar(103); - case 501: return QChar(103); - case 286: return QChar(103); - case 287: return QChar(103); - case 284: return QChar(103); - case 285: return QChar(103); - case 486: return QChar(103); - case 487: return QChar(103); - case 288: return QChar(103); - case 289: return QChar(103); - case 290: return QChar(103); - case 291: return QChar(103); - case 7712: return QChar(103); - case 7713: return QChar(103); - case 484: return QChar(103); - case 485: return QChar(103); - case 403: return QChar(103); - case 608: return QChar(103); - case 292: return QChar(104); - case 293: return QChar(104); - case 542: return QChar(104); - case 543: return QChar(104); - case 7718: return QChar(104); - case 7719: return QChar(104); - case 7714: return QChar(104); - case 7715: return QChar(104); - case 7720: return QChar(104); - case 7721: return QChar(104); - case 7716: return QChar(104); - case 7717: return QChar(104); - case 7722: return QChar(104); - case 7723: return QChar(104); - case 817: return QChar(104); - case 7830: return QChar(104); - case 294: return QChar(104); - case 295: return QChar(104); - case 11367: return QChar(104); - case 11368: return QChar(104); - case 205: return QChar(105); - case 237: return QChar(105); - case 204: return QChar(105); - case 236: return QChar(105); - case 300: return QChar(105); - case 301: return QChar(105); - case 206: return QChar(105); - case 238: return QChar(105); - case 463: return QChar(105); - case 464: return QChar(105); - case 207: return QChar(105); - case 239: return QChar(105); - case 7726: return QChar(105); - case 7727: return QChar(105); - case 296: return QChar(105); - case 297: return QChar(105); - case 304: return QChar(105); - case 302: return QChar(105); - case 303: return QChar(105); - case 298: return QChar(105); - case 299: return QChar(105); - case 7880: return QChar(105); - case 7881: return QChar(105); - case 520: return QChar(105); - case 521: return QChar(105); - case 522: return QChar(105); - case 523: return QChar(105); - case 7882: return QChar(105); - case 7883: return QChar(105); - case 7724: return QChar(105); - case 7725: return QChar(105); - case 305: return QChar(105); - case 407: return QChar(105); - case 616: return QChar(105); - case 308: return QChar(106); - case 309: return QChar(106); - case 780: return QChar(106); - case 496: return QChar(106); - case 567: return QChar(106); - case 584: return QChar(106); - case 585: return QChar(106); - case 669: return QChar(106); - case 607: return QChar(106); - case 644: return QChar(106); - case 7728: return QChar(107); - case 7729: return QChar(107); - case 488: return QChar(107); - case 489: return QChar(107); - case 310: return QChar(107); - case 311: return QChar(107); - case 7730: return QChar(107); - case 7731: return QChar(107); - case 7732: return QChar(107); - case 7733: return QChar(107); - case 408: return QChar(107); - case 409: return QChar(107); - case 11369: return QChar(107); - case 11370: return QChar(107); - case 313: return QChar(97); - case 314: return QChar(108); - case 317: return QChar(108); - case 318: return QChar(108); - case 315: return QChar(108); - case 316: return QChar(108); - case 7734: return QChar(108); - case 7735: return QChar(108); - case 7736: return QChar(108); - case 7737: return QChar(108); - case 7740: return QChar(108); - case 7741: return QChar(108); - case 7738: return QChar(108); - case 7739: return QChar(108); - case 321: return QChar(108); - case 322: return QChar(108); - case 803: return QChar(108); - case 319: return QChar(108); - case 320: return QChar(108); - case 573: return QChar(108); - case 410: return QChar(108); - case 11360: return QChar(108); - case 11361: return QChar(108); - case 11362: return QChar(108); - case 619: return QChar(108); - case 620: return QChar(108); - case 621: return QChar(108); - case 564: return QChar(108); - case 7742: return QChar(109); - case 7743: return QChar(109); - case 7744: return QChar(109); - case 7745: return QChar(109); - case 7746: return QChar(109); - case 7747: return QChar(109); - case 625: return QChar(109); - case 323: return QChar(110); - case 324: return QChar(110); - case 504: return QChar(110); - case 505: return QChar(110); - case 327: return QChar(110); - case 328: return QChar(110); - case 209: return QChar(110); - case 241: return QChar(110); - case 7748: return QChar(110); - case 7749: return QChar(110); - case 325: return QChar(110); - case 326: return QChar(110); - case 7750: return QChar(110); - case 7751: return QChar(110); - case 7754: return QChar(110); - case 7755: return QChar(110); - case 7752: return QChar(110); - case 7753: return QChar(110); - case 413: return QChar(110); - case 626: return QChar(110); - case 544: return QChar(110); - case 414: return QChar(110); - case 627: return QChar(110); - case 565: return QChar(110); - case 776: return QChar(116); - case 211: return QChar(111); - case 243: return QChar(111); - case 210: return QChar(111); - case 242: return QChar(111); - case 334: return QChar(111); - case 335: return QChar(111); - case 212: return QChar(111); - case 244: return QChar(111); - case 7888: return QChar(111); - case 7889: return QChar(111); - case 7890: return QChar(111); - case 7891: return QChar(111); - case 7894: return QChar(111); - case 7895: return QChar(111); - case 7892: return QChar(111); - case 7893: return QChar(111); - case 465: return QChar(111); - case 466: return QChar(111); - case 214: return QChar(111); - case 246: return QChar(111); - case 554: return QChar(111); - case 555: return QChar(111); - case 336: return QChar(111); - case 337: return QChar(111); - case 213: return QChar(111); - case 245: return QChar(111); - case 7756: return QChar(111); - case 7757: return QChar(111); - case 7758: return QChar(111); - case 7759: return QChar(111); - case 556: return QChar(111); - case 557: return QChar(111); - case 558: return QChar(111); - case 559: return QChar(111); - case 560: return QChar(111); - case 561: return QChar(111); - case 216: return QChar(111); - case 248: return QChar(111); - case 510: return QChar(111); - case 511: return QChar(111); - case 490: return QChar(111); - case 491: return QChar(111); - case 492: return QChar(111); - case 493: return QChar(111); - case 332: return QChar(111); - case 333: return QChar(111); - case 7762: return QChar(111); - case 7763: return QChar(111); - case 7760: return QChar(111); - case 7761: return QChar(111); - case 7886: return QChar(111); - case 7887: return QChar(111); - case 524: return QChar(111); - case 525: return QChar(111); - case 526: return QChar(111); - case 527: return QChar(111); - case 416: return QChar(111); - case 417: return QChar(111); - case 7898: return QChar(111); - case 7899: return QChar(111); - case 7900: return QChar(111); - case 7901: return QChar(111); - case 7904: return QChar(111); - case 7905: return QChar(111); - case 7902: return QChar(111); - case 7903: return QChar(111); - case 7906: return QChar(111); - case 7907: return QChar(111); - case 7884: return QChar(111); - case 7885: return QChar(111); - case 7896: return QChar(111); - case 7897: return QChar(111); - case 415: return QChar(111); - case 629: return QChar(111); - case 7764: return QChar(112); - case 7765: return QChar(112); - case 7766: return QChar(112); - case 7767: return QChar(112); - case 11363: return QChar(112); - case 420: return QChar(112); - case 421: return QChar(112); - case 771: return QChar(112); - case 672: return QChar(113); - case 586: return QChar(113); - case 587: return QChar(113); - case 340: return QChar(114); - case 341: return QChar(114); - case 344: return QChar(114); - case 345: return QChar(114); - case 7768: return QChar(114); - case 7769: return QChar(114); - case 342: return QChar(114); - case 343: return QChar(114); - case 528: return QChar(114); - case 529: return QChar(114); - case 530: return QChar(114); - case 531: return QChar(114); - case 7770: return QChar(114); - case 7771: return QChar(114); - case 7772: return QChar(114); - case 7773: return QChar(114); - case 7774: return QChar(114); - case 7775: return QChar(114); - case 588: return QChar(114); - case 589: return QChar(114); - case 7538: return QChar(114); - case 636: return QChar(114); - case 11364: return QChar(114); - case 637: return QChar(114); - case 638: return QChar(114); - case 7539: return QChar(114); - case 223: return QChar(115); - case 346: return QChar(115); - case 347: return QChar(115); - case 7780: return QChar(115); - case 7781: return QChar(115); - case 348: return QChar(115); - case 349: return QChar(115); - case 352: return QChar(115); - case 353: return QChar(115); - case 7782: return QChar(115); - case 7783: return QChar(115); - case 7776: return QChar(115); - case 7777: return QChar(115); - case 7835: return QChar(115); - case 350: return QChar(115); - case 351: return QChar(115); - case 7778: return QChar(115); - case 7779: return QChar(115); - case 7784: return QChar(115); - case 7785: return QChar(115); - case 536: return QChar(115); - case 537: return QChar(115); - case 642: return QChar(115); - case 809: return QChar(115); - case 222: return QChar(116); - case 254: return QChar(116); - case 356: return QChar(116); - case 357: return QChar(116); - case 7831: return QChar(116); - case 7786: return QChar(116); - case 7787: return QChar(116); - case 354: return QChar(116); - case 355: return QChar(116); - case 7788: return QChar(116); - case 7789: return QChar(116); - case 538: return QChar(116); - case 539: return QChar(116); - case 7792: return QChar(116); - case 7793: return QChar(116); - case 7790: return QChar(116); - case 7791: return QChar(116); - case 358: return QChar(116); - case 359: return QChar(116); - case 574: return QChar(116); - case 11366: return QChar(116); - case 7541: return QChar(116); - case 427: return QChar(116); - case 428: return QChar(116); - case 429: return QChar(116); - case 430: return QChar(116); - case 648: return QChar(116); - case 566: return QChar(116); - case 218: return QChar(117); - case 250: return QChar(117); - case 217: return QChar(117); - case 249: return QChar(117); - case 364: return QChar(117); - case 365: return QChar(117); - case 219: return QChar(117); - case 251: return QChar(117); - case 467: return QChar(117); - case 468: return QChar(117); - case 366: return QChar(117); - case 367: return QChar(117); - case 220: return QChar(117); - case 252: return QChar(117); - case 471: return QChar(117); - case 472: return QChar(117); - case 475: return QChar(117); - case 476: return QChar(117); - case 473: return QChar(117); - case 474: return QChar(117); - case 469: return QChar(117); - case 470: return QChar(117); - case 368: return QChar(117); - case 369: return QChar(117); - case 360: return QChar(117); - case 361: return QChar(117); - case 7800: return QChar(117); - case 7801: return QChar(117); - case 370: return QChar(117); - case 371: return QChar(117); - case 362: return QChar(117); - case 363: return QChar(117); - case 7802: return QChar(117); - case 7803: return QChar(117); - case 7910: return QChar(117); - case 7911: return QChar(117); - case 532: return QChar(117); - case 533: return QChar(117); - case 534: return QChar(117); - case 535: return QChar(117); - case 431: return QChar(117); - case 432: return QChar(117); - case 7912: return QChar(117); - case 7913: return QChar(117); - case 7914: return QChar(117); - case 7915: return QChar(117); - case 7918: return QChar(117); - case 7919: return QChar(117); - case 7916: return QChar(117); - case 7917: return QChar(117); - case 7920: return QChar(117); - case 7921: return QChar(117); - case 7908: return QChar(117); - case 7909: return QChar(117); - case 7794: return QChar(117); - case 7795: return QChar(117); - case 7798: return QChar(117); - case 7799: return QChar(117); - case 7796: return QChar(117); - case 7797: return QChar(117); - case 580: return QChar(117); - case 649: return QChar(117); - case 7804: return QChar(118); - case 7805: return QChar(118); - case 7806: return QChar(118); - case 7807: return QChar(118); - case 434: return QChar(118); - case 651: return QChar(118); - case 7810: return QChar(119); - case 7811: return QChar(119); - case 7808: return QChar(119); - case 7809: return QChar(119); - case 372: return QChar(119); - case 373: return QChar(119); - case 778: return QChar(121); - case 7832: return QChar(119); - case 7812: return QChar(119); - case 7813: return QChar(119); - case 7814: return QChar(119); - case 7815: return QChar(119); - case 7816: return QChar(119); - case 7817: return QChar(119); - case 7820: return QChar(120); - case 7821: return QChar(120); - case 7818: return QChar(120); - case 7819: return QChar(120); - case 221: return QChar(121); - case 253: return QChar(121); - case 7922: return QChar(121); - case 7923: return QChar(121); - case 374: return QChar(121); - case 375: return QChar(121); - case 7833: return QChar(121); - case 376: return QChar(121); - case 255: return QChar(121); - case 7928: return QChar(121); - case 7929: return QChar(121); - case 7822: return QChar(121); - case 7823: return QChar(121); - case 562: return QChar(121); - case 563: return QChar(121); - case 7926: return QChar(121); - case 7927: return QChar(121); - case 7924: return QChar(121); - case 7925: return QChar(121); - case 655: return QChar(121); - case 590: return QChar(121); - case 591: return QChar(121); - case 435: return QChar(121); - case 436: return QChar(121); - case 377: return QChar(122); - case 378: return QChar(122); - case 7824: return QChar(122); - case 7825: return QChar(122); - case 381: return QChar(122); - case 382: return QChar(122); - case 379: return QChar(122); - case 380: return QChar(122); - case 7826: return QChar(122); - case 7827: return QChar(122); - case 7828: return QChar(122); - case 7829: return QChar(122); - case 437: return QChar(122); - case 438: return QChar(122); - case 548: return QChar(122); - case 549: return QChar(122); - case 656: return QChar(122); - case 657: return QChar(122); - case 11371: return QChar(122); - case 11372: return QChar(122); - case 494: return QChar(122); - case 495: return QChar(122); - case 442: return QChar(122); - case 65298: return QChar(50); - case 65302: return QChar(54); - case 65314: return QChar(66); - case 65318: return QChar(70); - case 65322: return QChar(74); - case 65326: return QChar(78); - case 65330: return QChar(82); - case 65334: return QChar(86); - case 65338: return QChar(90); - case 65346: return QChar(98); - case 65350: return QChar(102); - case 65354: return QChar(106); - case 65358: return QChar(110); - case 65362: return QChar(114); - case 65366: return QChar(118); - case 65370: return QChar(122); - case 65297: return QChar(49); - case 65301: return QChar(53); - case 65305: return QChar(57); - case 65313: return QChar(65); - case 65317: return QChar(69); - case 65321: return QChar(73); - case 65325: return QChar(77); - case 65329: return QChar(81); - case 65333: return QChar(85); - case 65337: return QChar(89); - case 65345: return QChar(97); - case 65349: return QChar(101); - case 65353: return QChar(105); - case 65357: return QChar(109); - case 65361: return QChar(113); - case 65365: return QChar(117); - case 65369: return QChar(121); - case 65296: return QChar(48); - case 65300: return QChar(52); - case 65304: return QChar(56); - case 65316: return QChar(68); - case 65320: return QChar(72); - case 65324: return QChar(76); - case 65328: return QChar(80); - case 65332: return QChar(84); - case 65336: return QChar(88); - case 65348: return QChar(100); - case 65352: return QChar(104); - case 65356: return QChar(108); - case 65360: return QChar(112); - case 65364: return QChar(116); - case 65368: return QChar(120); - case 65299: return QChar(51); - case 65303: return QChar(55); - case 65315: return QChar(67); - case 65319: return QChar(71); - case 65323: return QChar(75); - case 65327: return QChar(79); - case 65331: return QChar(83); - case 65335: return QChar(87); - case 65347: return QChar(99); - case 65351: return QChar(103); - case 65355: return QChar(107); - case 65359: return QChar(111); - case 65363: return QChar(115); - case 65367: return QChar(119); - case 1105: return QChar(1077); - default: - break; - } - return QChar(0); - } -} - -QString textAccentFold(const QString &text) { - QString result(text); - bool copying = false; - int32 i = 0; - for (const QChar *s = text.unicode(), *ch = s, *e = text.unicode() + text.size(); ch != e; ++ch, ++i) { - if (ch->unicode() < 128) { - if (copying) result[i] = *ch; - continue; - } - if (chIsDiac(*ch)) { - copying = true; - --i; - continue; - } - if (ch->isHighSurrogate() && ch + 1 < e && (ch + 1)->isLowSurrogate()) { - QChar noAccent = chNoAccent(QChar::surrogateToUcs4(*ch, *(ch + 1))); - if (noAccent.unicode() > 0) { - copying = true; - result[i] = noAccent; - } else { - if (copying) result[i] = *ch; - ++ch, ++i; - if (copying) result[i] = *ch; - } - } else { - QChar noAccent = chNoAccent(ch->unicode()); - if (noAccent.unicode() > 0 && noAccent != *ch) { - result[i] = noAccent; - } else if (copying) { - result[i] = *ch; - } - } - } - return (i < result.size()) ? result.mid(0, i) : result; -} - -QString textSearchKey(const QString &text) { - return textAccentFold(text.trimmed().toLower()); -} - -bool textSplit(QString &sendingText, EntitiesInText &sendingEntities, QString &leftText, EntitiesInText &leftEntities, int32 limit) { - if (leftText.isEmpty() || !limit) return false; - - int32 currentEntity = 0, goodEntity = currentEntity, entityCount = leftEntities.size(); - bool goodInEntity = false, goodCanBreakEntity = false; - - int32 s = 0, half = limit / 2, goodLevel = 0; - for (const QChar *start = leftText.constData(), *ch = start, *end = leftText.constEnd(), *good = ch; ch != end; ++ch, ++s) { - while (currentEntity < entityCount && ch >= start + leftEntities[currentEntity].offset + leftEntities[currentEntity].length) { - ++currentEntity; - } - -#define MARK_GOOD_AS_LEVEL(level) \ -if (goodLevel <= (level)) {\ -goodLevel = (level);\ -good = ch;\ -goodEntity = currentEntity;\ -goodInEntity = inEntity;\ -goodCanBreakEntity = canBreakEntity;\ -} - - if (s > half) { - bool inEntity = (currentEntity < entityCount) && (ch > start + leftEntities[currentEntity].offset) && (ch < start + leftEntities[currentEntity].offset + leftEntities[currentEntity].length); - EntityInTextType entityType = (currentEntity < entityCount) ? leftEntities[currentEntity].type : EntityInTextBold; - bool canBreakEntity = (entityType == EntityInTextPre || entityType == EntityInTextCode); - int32 noEntityLevel = inEntity ? 0 : 1; - if (inEntity && !canBreakEntity) { - MARK_GOOD_AS_LEVEL(0); - } else { - if (chIsNewline(*ch)) { - if (inEntity) { - if (ch + 1 < end && chIsNewline(*(ch + 1))) { - MARK_GOOD_AS_LEVEL(12); - } else { - MARK_GOOD_AS_LEVEL(11); - } - } else if (ch + 1 < end && chIsNewline(*(ch + 1))) { - MARK_GOOD_AS_LEVEL(15); - } else if (currentEntity < entityCount && ch + 1 == start + leftEntities[currentEntity].offset && leftEntities[currentEntity].type == EntityInTextPre) { - MARK_GOOD_AS_LEVEL(14); - } else if (currentEntity > 0 && ch == start + leftEntities[currentEntity - 1].offset + leftEntities[currentEntity - 1].length && leftEntities[currentEntity - 1].type == EntityInTextPre) { - MARK_GOOD_AS_LEVEL(14); - } else { - MARK_GOOD_AS_LEVEL(13); - } - } else if (chIsSpace(*ch)) { - if (chIsSentenceEnd(*(ch - 1))) { - MARK_GOOD_AS_LEVEL(9 + noEntityLevel); - } else if (chIsSentencePartEnd(*(ch - 1))) { - MARK_GOOD_AS_LEVEL(7 + noEntityLevel); - } else { - MARK_GOOD_AS_LEVEL(5 + noEntityLevel); - } - } else if (chIsWordSeparator(*(ch - 1))) { - MARK_GOOD_AS_LEVEL(3 + noEntityLevel); - } else { - MARK_GOOD_AS_LEVEL(1 + noEntityLevel); - } - } - } - -#undef MARK_GOOD_AS_LEVEL - - int elen = 0; - if (EmojiPtr e = emojiFromText(ch, end, &elen)) { - for (int i = 0; i < elen; ++i, ++ch, ++s) { - if (ch->isHighSurrogate() && i + 1 < elen && (ch + 1)->isLowSurrogate()) { - ++ch; - ++i; - } - } - --ch; - --s; - } else if (ch->isHighSurrogate() && ch + 1 < end && (ch + 1)->isLowSurrogate()) { - ++ch; - } - if (s >= limit) { - sendingText = leftText.mid(0, good - start); - leftText = leftText.mid(good - start); - if (goodInEntity) { - if (goodCanBreakEntity) { - sendingEntities = leftEntities.mid(0, goodEntity + 1); - sendingEntities.back().length = good - start - sendingEntities.back().offset; - leftEntities = leftEntities.mid(goodEntity); - for (EntitiesInText::iterator i = leftEntities.begin(), e = leftEntities.end(); i != e; ++i) { - i->offset -= good - start; - if (i->offset < 0) { - i->length += i->offset; - i->offset = 0; - } - } - } else { - sendingEntities = leftEntities.mid(0, goodEntity); - leftEntities = leftEntities.mid(goodEntity + 1); - } - } else { - sendingEntities = leftEntities.mid(0, goodEntity); - leftEntities = leftEntities.mid(goodEntity); - for (EntitiesInText::iterator i = leftEntities.begin(), e = leftEntities.end(); i != e; ++i) { - i->offset -= good - start; - } - } - return true; - } - } - sendingText = leftText; - leftText = QString(); - sendingEntities = leftEntities; - leftEntities = EntitiesInText(); - return true; -} - -bool textcmdStartsLink(const QChar *start, int32 len, int32 commandOffset) { - if (commandOffset + 2 < len) { - if (*(start + commandOffset + 1) == TextCommandLinkIndex) { - return (*(start + commandOffset + 2) != 0); - } - return (*(start + commandOffset + 1) != TextCommandLinkText); - } - return false; -} - -bool checkTagStartInCommand(const QChar *start, int32 len, int32 tagStart, int32 &commandOffset, bool &commandIsLink, bool &inLink) { - bool inCommand = false; - const QChar *commandEnd = start + commandOffset; - while (commandOffset < len && tagStart > commandOffset) { // skip commands, evaluating are we in link or not - commandEnd = textSkipCommand(start + commandOffset, start + len); - if (commandEnd > start + commandOffset) { - if (tagStart < (commandEnd - start)) { - inCommand = true; - break; - } - for (commandOffset = commandEnd - start; commandOffset < len; ++commandOffset) { - if (*(start + commandOffset) == TextCommand) { - inLink = commandIsLink; - commandIsLink = textcmdStartsLink(start, len, commandOffset); - break; - } - } - if (commandOffset >= len) { - inLink = commandIsLink; - commandIsLink = false; - } - } else { - break; - } - } - if (inCommand) { - commandOffset = commandEnd - start; - } - return inCommand; -} - -EntitiesInText textParseEntities(QString &text, int32 flags, bool rich) { // some code is duplicated in flattextarea.cpp! - EntitiesInText result, mono; - - bool withHashtags = (flags & TextParseHashtags); - bool withMentions = (flags & TextParseMentions); - bool withBotCommands = (flags & TextParseBotCommands); - bool withMono = (flags & TextParseMono); - - if (withMono) { // parse mono entities (code and pre) - QString newText; - - int32 offset = 0, matchOffset = offset, len = text.size(), commandOffset = rich ? 0 : len; - bool inLink = false, commandIsLink = false; - const QChar *start = text.constData(); - for (; matchOffset < len;) { - if (commandOffset <= matchOffset) { - for (commandOffset = matchOffset; commandOffset < len; ++commandOffset) { - if (*(start + commandOffset) == TextCommand) { - inLink = commandIsLink; - commandIsLink = textcmdStartsLink(start, len, commandOffset); - break; - } - } - if (commandOffset >= len) { - inLink = commandIsLink; - commandIsLink = false; - } - } - QRegularExpressionMatch mPre = _rePre.match(text, matchOffset); - QRegularExpressionMatch mCode = _reCode.match(text, matchOffset), mTag; - if (!mPre.hasMatch() && !mCode.hasMatch()) break; - - int32 preStart = mPre.hasMatch() ? mPre.capturedStart() : INT_MAX, - preEnd = mPre.hasMatch() ? mPre.capturedEnd() : INT_MAX, - codeStart = mCode.hasMatch() ? mCode.capturedStart() : INT_MAX, - codeEnd = mCode.hasMatch() ? mCode.capturedEnd() : INT_MAX, - tagStart, tagEnd; - if (mPre.hasMatch()) { - if (!mPre.capturedRef(1).isEmpty()) { - ++preStart; - } - if (!mPre.capturedRef(4).isEmpty()) { - --preEnd; - } - } - if (mCode.hasMatch()) { - if (!mCode.capturedRef(1).isEmpty()) { - ++codeStart; - } - if (!mCode.capturedRef(4).isEmpty()) { - --codeEnd; - } - } - - bool pre = (preStart <= codeStart); - if (pre) { - tagStart = preStart; - tagEnd = preEnd; - mTag = mPre; - } else { - tagStart = codeStart; - tagEnd = codeEnd; - mTag = mCode; - } - - bool inCommand = checkTagStartInCommand(start, len, tagStart, commandOffset, commandIsLink, inLink); - if (inCommand || inLink) { - matchOffset = commandOffset; - continue; - } - - if (newText.isEmpty()) newText.reserve(text.size()); - - bool addNewlineBefore = false, addNewlineAfter = false; - int32 outerStart = tagStart, outerEnd = tagEnd; - int32 innerStart = tagStart + mTag.capturedLength(2), innerEnd = tagEnd - mTag.capturedLength(3); - if (pre) { - while (outerStart > 0 && chIsSpace(*(start + outerStart - 1), rich) && !chIsNewline(*(start + outerStart - 1))) { - --outerStart; - } - addNewlineBefore = (outerStart > 0 && !chIsNewline(*(start + outerStart - 1))); - - for (int32 testInnerStart = innerStart; testInnerStart < innerEnd; ++testInnerStart) { - if (chIsNewline(*(start + testInnerStart))) { - innerStart = testInnerStart + 1; - break; - } else if (!chIsSpace(*(start + testInnerStart))) { - break; - } - } - for (int32 testInnerEnd = innerEnd; innerStart < testInnerEnd;) { - --testInnerEnd; - if (chIsNewline(*(start + testInnerEnd))) { - innerEnd = testInnerEnd; - break; - } else if (!chIsSpace(*(start + testInnerEnd))) { - break; - } - } - - while (outerEnd < len && chIsSpace(*(start + outerEnd)) && !chIsNewline(*(start + outerEnd))) { - ++outerEnd; - } - addNewlineAfter = (outerEnd < len && !chIsNewline(*(start + outerEnd))); - } - if (outerStart > offset) newText.append(start + offset, outerStart - offset); - if (addNewlineBefore) newText.append('\n'); - - int32 tagLength = innerEnd - innerStart; - mono.push_back(EntityInText(pre ? EntityInTextPre : EntityInTextCode, newText.size(), tagLength)); - - newText.append(start + innerStart, tagLength); - if (addNewlineAfter) newText.append('\n'); - - offset = matchOffset = outerEnd; - } - if (!newText.isEmpty()) { - newText.append(start + offset, len - offset); - text = newText; - } - } - int32 monoEntity = 0, monoCount = mono.size(), monoTill = 0; - - initLinkSets(); - int32 len = text.size(), commandOffset = rich ? 0 : len; - bool inLink = false, commandIsLink = false; - const QChar *start = text.constData(), *end = start + text.size(); - for (int32 offset = 0, matchOffset = offset, mentionSkip = 0; offset < len;) { - if (commandOffset <= offset) { - for (commandOffset = offset; commandOffset < len; ++commandOffset) { - if (*(start + commandOffset) == TextCommand) { - inLink = commandIsLink; - commandIsLink = textcmdStartsLink(start, len, commandOffset); - break; - } - } - } - QRegularExpressionMatch mDomain = _reDomain.match(text, matchOffset); - QRegularExpressionMatch mExplicitDomain = _reExplicitDomain.match(text, matchOffset); - QRegularExpressionMatch mHashtag = withHashtags ? _reHashtag.match(text, matchOffset) : QRegularExpressionMatch(); - QRegularExpressionMatch mMention = withMentions ? _reMention.match(text, qMax(mentionSkip, matchOffset)) : QRegularExpressionMatch(); - QRegularExpressionMatch mBotCommand = withBotCommands ? _reBotCommand.match(text, matchOffset) : QRegularExpressionMatch(); - - EntityInTextType lnkType = EntityInTextUrl; - int32 lnkStart = 0, lnkLength = 0; - int32 domainStart = mDomain.hasMatch() ? mDomain.capturedStart() : INT_MAX, - domainEnd = mDomain.hasMatch() ? mDomain.capturedEnd() : INT_MAX, - explicitDomainStart = mExplicitDomain.hasMatch() ? mExplicitDomain.capturedStart() : INT_MAX, - explicitDomainEnd = mExplicitDomain.hasMatch() ? mExplicitDomain.capturedEnd() : INT_MAX, - hashtagStart = mHashtag.hasMatch() ? mHashtag.capturedStart() : INT_MAX, - hashtagEnd = mHashtag.hasMatch() ? mHashtag.capturedEnd() : INT_MAX, - mentionStart = mMention.hasMatch() ? mMention.capturedStart() : INT_MAX, - mentionEnd = mMention.hasMatch() ? mMention.capturedEnd() : INT_MAX, - botCommandStart = mBotCommand.hasMatch() ? mBotCommand.capturedStart() : INT_MAX, - botCommandEnd = mBotCommand.hasMatch() ? mBotCommand.capturedEnd() : INT_MAX; - if (mHashtag.hasMatch()) { - if (!mHashtag.capturedRef(1).isEmpty()) { - ++hashtagStart; - } - if (!mHashtag.capturedRef(2).isEmpty()) { - --hashtagEnd; - } - } - while (mMention.hasMatch()) { - if (!mMention.capturedRef(1).isEmpty()) { - ++mentionStart; - } - if (!mMention.capturedRef(2).isEmpty()) { - --mentionEnd; - } - if (!(start + mentionStart + 1)->isLetter() || !(start + mentionEnd - 1)->isLetterOrNumber()) { - mentionSkip = mentionEnd; - mMention = _reMention.match(text, qMax(mentionSkip, matchOffset)); - if (mMention.hasMatch()) { - mentionStart = mMention.capturedStart(); - mentionEnd = mMention.capturedEnd(); - } else { - mentionStart = INT_MAX; - mentionEnd = INT_MAX; - } - } else { - break; - } - } - if (mBotCommand.hasMatch()) { - if (!mBotCommand.capturedRef(1).isEmpty()) { - ++botCommandStart; - } - if (!mBotCommand.capturedRef(3).isEmpty()) { - --botCommandEnd; - } - } - if (!mDomain.hasMatch() && !mExplicitDomain.hasMatch() && !mHashtag.hasMatch() && !mMention.hasMatch() && !mBotCommand.hasMatch()) { - break; - } - - if (explicitDomainStart < domainStart) { - domainStart = explicitDomainStart; - domainEnd = explicitDomainEnd; - mDomain = mExplicitDomain; - } - if (mentionStart < hashtagStart && mentionStart < domainStart && mentionStart < botCommandStart) { - bool inCommand = checkTagStartInCommand(start, len, mentionStart, commandOffset, commandIsLink, inLink); - if (inCommand || inLink) { - offset = matchOffset = commandOffset; - continue; - } - - lnkType = EntityInTextMention; - lnkStart = mentionStart; - lnkLength = mentionEnd - mentionStart; - } else if (hashtagStart < domainStart && hashtagStart < botCommandStart) { - bool inCommand = checkTagStartInCommand(start, len, hashtagStart, commandOffset, commandIsLink, inLink); - if (inCommand || inLink) { - offset = matchOffset = commandOffset; - continue; - } - - lnkType = EntityInTextHashtag; - lnkStart = hashtagStart; - lnkLength = hashtagEnd - hashtagStart; - } else if (botCommandStart < domainStart) { - bool inCommand = checkTagStartInCommand(start, len, botCommandStart, commandOffset, commandIsLink, inLink); - if (inCommand || inLink) { - offset = matchOffset = commandOffset; - continue; - } - - lnkType = EntityInTextBotCommand; - lnkStart = botCommandStart; - lnkLength = botCommandEnd - botCommandStart; - } else { - bool inCommand = checkTagStartInCommand(start, len, domainStart, commandOffset, commandIsLink, inLink); - if (inCommand || inLink) { - offset = matchOffset = commandOffset; - continue; - } - - QString protocol = mDomain.captured(1).toLower(); - QString topDomain = mDomain.captured(3).toLower(); - - bool isProtocolValid = protocol.isEmpty() || _validProtocols.contains(hashCrc32(protocol.constData(), protocol.size() * sizeof(QChar))); - bool isTopDomainValid = !protocol.isEmpty() || _validTopDomains.contains(hashCrc32(topDomain.constData(), topDomain.size() * sizeof(QChar))); - - if (protocol.isEmpty() && domainStart > offset + 1 && *(start + domainStart - 1) == QChar('@')) { - QString forMailName = text.mid(offset, domainStart - offset - 1); - QRegularExpressionMatch mMailName = _reMailName.match(forMailName); - if (mMailName.hasMatch()) { - int32 mailStart = offset + mMailName.capturedStart(); - if (mailStart < offset) { - mailStart = offset; - } - lnkType = EntityInTextEmail; - lnkStart = mailStart; - lnkLength = domainEnd - mailStart; - } - } - if (lnkType == EntityInTextUrl && !lnkLength) { - if (!isProtocolValid || !isTopDomainValid) { - matchOffset = domainEnd; - continue; - } - lnkStart = domainStart; - - QStack parenth; - const QChar *domainEnd = start + mDomain.capturedEnd(), *p = domainEnd; - for (; p < end; ++p) { - QChar ch(*p); - if (chIsLinkEnd(ch)) break; // link finished - if (chIsAlmostLinkEnd(ch)) { - const QChar *endTest = p + 1; - while (endTest < end && chIsAlmostLinkEnd(*endTest)) { - ++endTest; - } - if (endTest >= end || chIsLinkEnd(*endTest)) { - break; // link finished at p - } - p = endTest; - ch = *p; - } - if (ch == '(' || ch == '[' || ch == '{' || ch == '<') { - parenth.push(p); - } else if (ch == ')' || ch == ']' || ch == '}' || ch == '>') { - if (parenth.isEmpty()) break; - const QChar *q = parenth.pop(), open(*q); - if ((ch == ')' && open != '(') || (ch == ']' && open != '[') || (ch == '}' && open != '{') || (ch == '>' && open != '<')) { - p = q; - break; - } - } - } - if (p > domainEnd) { // check, that domain ended - if (domainEnd->unicode() != '/' && domainEnd->unicode() != '?') { - matchOffset = domainEnd - start; - continue; - } - } - lnkLength = (p - start) - lnkStart; - } - } - for (; monoEntity < monoCount && mono[monoEntity].offset <= lnkStart; ++monoEntity) { - monoTill = qMax(monoTill, mono[monoEntity].offset + mono[monoEntity].length); - result.push_back(mono[monoEntity]); - } - if (lnkStart >= monoTill) { - result.push_back(EntityInText(lnkType, lnkStart, lnkLength)); - } - - offset = matchOffset = lnkStart + lnkLength; - } - for (; monoEntity < monoCount; ++monoEntity) { - monoTill = qMax(monoTill, mono[monoEntity].offset + mono[monoEntity].length); - result.push_back(mono[monoEntity]); - } - - return result; -} - -QString textApplyEntities(const QString &text, const EntitiesInText &entities) { - if (entities.isEmpty()) return text; - - QMultiMap closingTags; - QString code(qsl("`")), pre(qsl("```")); - - QString result; - int32 size = text.size(); - const QChar *b = text.constData(), *already = b, *e = b + size; - EntitiesInText::const_iterator entity = entities.cbegin(), end = entities.cend(); - while (entity != end && ((entity->type != EntityInTextCode && entity->type != EntityInTextPre) || entity->length <= 0 || entity->offset >= size)) { - ++entity; - } - while (entity != end || !closingTags.isEmpty()) { - int32 nextOpenEntity = (entity == end) ? (size + 1) : entity->offset; - int32 nextCloseEntity = closingTags.isEmpty() ? (size + 1) : closingTags.cbegin().key(); - if (nextOpenEntity <= nextCloseEntity) { - QString tag = (entity->type == EntityInTextCode) ? code : pre; - if (result.isEmpty()) result.reserve(text.size() + entities.size() * pre.size() * 2); - - const QChar *offset = b + nextOpenEntity; - if (offset > already) { - result.append(already, offset - already); - already = offset; - } - result.append(tag); - closingTags.insert(qMin(entity->offset + entity->length, size), tag); - - ++entity; - while (entity != end && ((entity->type != EntityInTextCode && entity->type != EntityInTextPre) || entity->length <= 0 || entity->offset >= size)) { - ++entity; - } - } else { - const QChar *offset = b + nextCloseEntity; - if (offset > already) { - result.append(already, offset - already); - already = offset; - } - result.append(closingTags.cbegin().value()); - closingTags.erase(closingTags.begin()); - } - } - if (result.isEmpty()) { - return text; - } - const QChar *offset = b + size; - if (offset > already) { - result.append(already, offset - already); - } - return result; -} - void emojiDraw(QPainter &p, EmojiPtr e, int x, int y) { p.drawPixmap(QPoint(x, y), App::emoji(), QRect(e->x * ESize, e->y * ESize, ESize, ESize)); } - -void replaceStringWithEntities(const QLatin1String &from, QChar to, QString &result, EntitiesInText &entities, bool checkSpace = false) { - int32 len = from.size(), s = result.size(), offset = 0, length = 0; - EntitiesInText::iterator i = entities.begin(), e = entities.end(); - for (QChar *start = result.data(); offset < s;) { - int32 nextOffset = result.indexOf(from, offset); - if (nextOffset < 0) { - moveStringPart(start, length, offset, s - offset, entities); - break; - } - - if (checkSpace) { - bool spaceBefore = (nextOffset > 0) && (start + nextOffset - 1)->isSpace(); - bool spaceAfter = (nextOffset + len < s) && (start + nextOffset + len)->isSpace(); - if (!spaceBefore && !spaceAfter) { - moveStringPart(start, length, offset, nextOffset - offset + len + 1, entities); - continue; - } - } - - bool skip = false; - for (; i != e; ++i) { // find and check next finishing entity - if (i->offset + i->length > nextOffset) { - skip = (i->offset < nextOffset + len); - break; - } - } - if (skip) { - moveStringPart(start, length, offset, nextOffset - offset + len, entities); - continue; - } - - moveStringPart(start, length, offset, nextOffset - offset, entities); - - *(start + length) = to; - ++length; - offset += len; - } - if (length < s) result.resize(length); -} - -QString prepareTextWithEntities(QString result, EntitiesInText &entities, int32 flags) { - cleanTextWithEntities(result, entities); - - if (flags) { - entities = textParseEntities(result, flags); - } - - replaceStringWithEntities(qstr("--"), QChar(8212), result, entities, true); - replaceStringWithEntities(qstr("<<"), QChar(171), result, entities); - replaceStringWithEntities(qstr(">>"), QChar(187), result, entities); - - if (cReplaceEmojis()) { - result = replaceEmojis(result, entities); - } - - trimTextWithEntities(result, entities); - - return result; -} diff --git a/Telegram/SourceFiles/ui/text.h b/Telegram/SourceFiles/ui/text/text.h similarity index 51% rename from Telegram/SourceFiles/ui/text.h rename to Telegram/SourceFiles/ui/text/text.h index 6f73c5dae..0c9cf24e3 100644 --- a/Telegram/SourceFiles/ui/text.h +++ b/Telegram/SourceFiles/ui/text/text.h @@ -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 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 &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 linksToMTP(const EntitiesInText &links, bool sending = false) { - MTPVector result(MTP_vector(0)); - QVector &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 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 TextCustomTag; // open str and close str typedef QMap 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); diff --git a/Telegram/SourceFiles/ui/text/text_block.cpp b/Telegram/SourceFiles/ui/text/text_block.cpp new file mode 100644 index 000000000..0505636b5 --- /dev/null +++ b/Telegram/SourceFiles/ui/text/text_block.cpp @@ -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() % 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; +} diff --git a/Telegram/SourceFiles/ui/text/text_block.h b/Telegram/SourceFiles/ui/text/text_block.h new file mode 100644 index 000000000..c5afc4485 --- /dev/null +++ b/Telegram/SourceFiles/ui/text/text_block.h @@ -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 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; +}; diff --git a/Telegram/SourceFiles/ui/text/text_entity.cpp b/Telegram/SourceFiles/ui/text/text_entity.cpp new file mode 100644 index 000000000..bc241a950 --- /dev/null +++ b/Telegram/SourceFiles/ui/text/text_entity.cpp @@ -0,0 +1,1919 @@ +/* +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_entity.h" + +namespace { + +const QRegularExpression _reDomain(QString::fromUtf8("(?|'\"\\[\\]\\{\\}`\\~\\!\\%\\^\\*\\(\\)\\-\\+=\\x10])#[\\w]{2,64}([\\W]|$)"), QRegularExpression::UseUnicodePropertiesOption); +const QRegularExpression _reMention(qsl("(^|[\\s\\.,:;<>|'\"\\[\\]\\{\\}`\\~\\!\\%\\^\\*\\(\\)\\-\\+=\\x10])@[A-Za-z_0-9]{1,32}([\\W]|$)"), QRegularExpression::UseUnicodePropertiesOption); +const QRegularExpression _reBotCommand(qsl("(^|[\\s\\.,:;<>|'\"\\[\\]\\{\\}`\\~\\!\\%\\^\\*\\(\\)\\-\\+=\\x10])/[A-Za-z_0-9]{1,64}(@[A-Za-z_0-9]{5,32})?([\\W]|$)")); +const QRegularExpression _rePre(qsl("(^|[\\s\\.,:;<>|'\"\\[\\]\\{\\}`\\~\\!\\?\\%\\^\\*\\(\\)\\-\\+=\\x10])(````?)[\\s\\S]+?(````?)([\\s\\.,:;<>|'\"\\[\\]\\{\\}`\\~\\!\\?\\%\\^\\*\\(\\)\\-\\+=\\x10]|$)"), QRegularExpression::UseUnicodePropertiesOption); +const QRegularExpression _reCode(qsl("(^|[\\s\\.,:;<>|'\"\\[\\]\\{\\}`\\~\\!\\?\\%\\^\\*\\(\\)\\-\\+=\\x10])(`)[^\\n]+?(`)([\\s\\.,:;<>|'\"\\[\\]\\{\\}`\\~\\!\\?\\%\\^\\*\\(\\)\\-\\+=\\x10]|$)"), QRegularExpression::UseUnicodePropertiesOption); +QSet _validProtocols, _validTopDomains; + +} // namespace + +const QRegularExpression &reDomain() { + return _reDomain; +} + +const QRegularExpression &reMailName() { + return _reMailName; +} + +const QRegularExpression &reMailStart() { + return _reMailStart; +} + +const QRegularExpression &reHashtag() { + return _reHashtag; +} + +const QRegularExpression &reBotCommand() { + return _reBotCommand; +} + +namespace { + +void regOneProtocol(const QString &protocol) { + _validProtocols.insert(hashCrc32(protocol.constData(), protocol.size() * sizeof(QChar))); +} + +void regOneTopDomain(const QString &domain) { + _validTopDomains.insert(hashCrc32(domain.constData(), domain.size() * sizeof(QChar))); +} + +} // namespace + +const QSet &validProtocols() { + return _validProtocols; +} +const QSet &validTopDomains() { + return _validTopDomains; +} + +void initLinkSets() { + if (!_validProtocols.isEmpty() || !_validTopDomains.isEmpty()) return; + + regOneProtocol(qsl("itmss")); // itunes + regOneProtocol(qsl("http")); + regOneProtocol(qsl("https")); + regOneProtocol(qsl("ftp")); + regOneProtocol(qsl("tg")); // local urls + + regOneTopDomain(qsl("ac")); + regOneTopDomain(qsl("ad")); + regOneTopDomain(qsl("ae")); + regOneTopDomain(qsl("af")); + regOneTopDomain(qsl("ag")); + regOneTopDomain(qsl("ai")); + regOneTopDomain(qsl("al")); + regOneTopDomain(qsl("am")); + regOneTopDomain(qsl("an")); + regOneTopDomain(qsl("ao")); + regOneTopDomain(qsl("aq")); + regOneTopDomain(qsl("ar")); + regOneTopDomain(qsl("as")); + regOneTopDomain(qsl("at")); + regOneTopDomain(qsl("au")); + regOneTopDomain(qsl("aw")); + regOneTopDomain(qsl("ax")); + regOneTopDomain(qsl("az")); + regOneTopDomain(qsl("ba")); + regOneTopDomain(qsl("bb")); + regOneTopDomain(qsl("bd")); + regOneTopDomain(qsl("be")); + regOneTopDomain(qsl("bf")); + regOneTopDomain(qsl("bg")); + regOneTopDomain(qsl("bh")); + regOneTopDomain(qsl("bi")); + regOneTopDomain(qsl("bj")); + regOneTopDomain(qsl("bm")); + regOneTopDomain(qsl("bn")); + regOneTopDomain(qsl("bo")); + regOneTopDomain(qsl("br")); + regOneTopDomain(qsl("bs")); + regOneTopDomain(qsl("bt")); + regOneTopDomain(qsl("bv")); + regOneTopDomain(qsl("bw")); + regOneTopDomain(qsl("by")); + regOneTopDomain(qsl("bz")); + regOneTopDomain(qsl("ca")); + regOneTopDomain(qsl("cc")); + regOneTopDomain(qsl("cd")); + regOneTopDomain(qsl("cf")); + regOneTopDomain(qsl("cg")); + regOneTopDomain(qsl("ch")); + regOneTopDomain(qsl("ci")); + regOneTopDomain(qsl("ck")); + regOneTopDomain(qsl("cl")); + regOneTopDomain(qsl("cm")); + regOneTopDomain(qsl("cn")); + regOneTopDomain(qsl("co")); + regOneTopDomain(qsl("cr")); + regOneTopDomain(qsl("cu")); + regOneTopDomain(qsl("cv")); + regOneTopDomain(qsl("cx")); + regOneTopDomain(qsl("cy")); + regOneTopDomain(qsl("cz")); + regOneTopDomain(qsl("de")); + regOneTopDomain(qsl("dj")); + regOneTopDomain(qsl("dk")); + regOneTopDomain(qsl("dm")); + regOneTopDomain(qsl("do")); + regOneTopDomain(qsl("dz")); + regOneTopDomain(qsl("ec")); + regOneTopDomain(qsl("ee")); + regOneTopDomain(qsl("eg")); + regOneTopDomain(qsl("eh")); + regOneTopDomain(qsl("er")); + regOneTopDomain(qsl("es")); + regOneTopDomain(qsl("et")); + regOneTopDomain(qsl("eu")); + regOneTopDomain(qsl("fi")); + regOneTopDomain(qsl("fj")); + regOneTopDomain(qsl("fk")); + regOneTopDomain(qsl("fm")); + regOneTopDomain(qsl("fo")); + regOneTopDomain(qsl("fr")); + regOneTopDomain(qsl("ga")); + regOneTopDomain(qsl("gd")); + regOneTopDomain(qsl("ge")); + regOneTopDomain(qsl("gf")); + regOneTopDomain(qsl("gg")); + regOneTopDomain(qsl("gh")); + regOneTopDomain(qsl("gi")); + regOneTopDomain(qsl("gl")); + regOneTopDomain(qsl("gm")); + regOneTopDomain(qsl("gn")); + regOneTopDomain(qsl("gp")); + regOneTopDomain(qsl("gq")); + regOneTopDomain(qsl("gr")); + regOneTopDomain(qsl("gs")); + regOneTopDomain(qsl("gt")); + regOneTopDomain(qsl("gu")); + regOneTopDomain(qsl("gw")); + regOneTopDomain(qsl("gy")); + regOneTopDomain(qsl("hk")); + regOneTopDomain(qsl("hm")); + regOneTopDomain(qsl("hn")); + regOneTopDomain(qsl("hr")); + regOneTopDomain(qsl("ht")); + regOneTopDomain(qsl("hu")); + regOneTopDomain(qsl("id")); + regOneTopDomain(qsl("ie")); + regOneTopDomain(qsl("il")); + regOneTopDomain(qsl("im")); + regOneTopDomain(qsl("in")); + regOneTopDomain(qsl("io")); + regOneTopDomain(qsl("iq")); + regOneTopDomain(qsl("ir")); + regOneTopDomain(qsl("is")); + regOneTopDomain(qsl("it")); + regOneTopDomain(qsl("je")); + regOneTopDomain(qsl("jm")); + regOneTopDomain(qsl("jo")); + regOneTopDomain(qsl("jp")); + regOneTopDomain(qsl("ke")); + regOneTopDomain(qsl("kg")); + regOneTopDomain(qsl("kh")); + regOneTopDomain(qsl("ki")); + regOneTopDomain(qsl("km")); + regOneTopDomain(qsl("kn")); + regOneTopDomain(qsl("kp")); + regOneTopDomain(qsl("kr")); + regOneTopDomain(qsl("kw")); + regOneTopDomain(qsl("ky")); + regOneTopDomain(qsl("kz")); + regOneTopDomain(qsl("la")); + regOneTopDomain(qsl("lb")); + regOneTopDomain(qsl("lc")); + regOneTopDomain(qsl("li")); + regOneTopDomain(qsl("lk")); + regOneTopDomain(qsl("lr")); + regOneTopDomain(qsl("ls")); + regOneTopDomain(qsl("lt")); + regOneTopDomain(qsl("lu")); + regOneTopDomain(qsl("lv")); + regOneTopDomain(qsl("ly")); + regOneTopDomain(qsl("ma")); + regOneTopDomain(qsl("mc")); + regOneTopDomain(qsl("md")); + regOneTopDomain(qsl("me")); + regOneTopDomain(qsl("mg")); + regOneTopDomain(qsl("mh")); + regOneTopDomain(qsl("mk")); + regOneTopDomain(qsl("ml")); + regOneTopDomain(qsl("mm")); + regOneTopDomain(qsl("mn")); + regOneTopDomain(qsl("mo")); + regOneTopDomain(qsl("mp")); + regOneTopDomain(qsl("mq")); + regOneTopDomain(qsl("mr")); + regOneTopDomain(qsl("ms")); + regOneTopDomain(qsl("mt")); + regOneTopDomain(qsl("mu")); + regOneTopDomain(qsl("mv")); + regOneTopDomain(qsl("mw")); + regOneTopDomain(qsl("mx")); + regOneTopDomain(qsl("my")); + regOneTopDomain(qsl("mz")); + regOneTopDomain(qsl("na")); + regOneTopDomain(qsl("nc")); + regOneTopDomain(qsl("ne")); + regOneTopDomain(qsl("nf")); + regOneTopDomain(qsl("ng")); + regOneTopDomain(qsl("ni")); + regOneTopDomain(qsl("nl")); + regOneTopDomain(qsl("no")); + regOneTopDomain(qsl("np")); + regOneTopDomain(qsl("nr")); + regOneTopDomain(qsl("nu")); + regOneTopDomain(qsl("nz")); + regOneTopDomain(qsl("om")); + regOneTopDomain(qsl("pa")); + regOneTopDomain(qsl("pe")); + regOneTopDomain(qsl("pf")); + regOneTopDomain(qsl("pg")); + regOneTopDomain(qsl("ph")); + regOneTopDomain(qsl("pk")); + regOneTopDomain(qsl("pl")); + regOneTopDomain(qsl("pm")); + regOneTopDomain(qsl("pn")); + regOneTopDomain(qsl("pr")); + regOneTopDomain(qsl("ps")); + regOneTopDomain(qsl("pt")); + regOneTopDomain(qsl("pw")); + regOneTopDomain(qsl("py")); + regOneTopDomain(qsl("qa")); + regOneTopDomain(qsl("re")); + regOneTopDomain(qsl("ro")); + regOneTopDomain(qsl("ru")); + regOneTopDomain(qsl("rs")); + regOneTopDomain(qsl("rw")); + regOneTopDomain(qsl("sa")); + regOneTopDomain(qsl("sb")); + regOneTopDomain(qsl("sc")); + regOneTopDomain(qsl("sd")); + regOneTopDomain(qsl("se")); + regOneTopDomain(qsl("sg")); + regOneTopDomain(qsl("sh")); + regOneTopDomain(qsl("si")); + regOneTopDomain(qsl("sj")); + regOneTopDomain(qsl("sk")); + regOneTopDomain(qsl("sl")); + regOneTopDomain(qsl("sm")); + regOneTopDomain(qsl("sn")); + regOneTopDomain(qsl("so")); + regOneTopDomain(qsl("sr")); + regOneTopDomain(qsl("ss")); + regOneTopDomain(qsl("st")); + regOneTopDomain(qsl("su")); + regOneTopDomain(qsl("sv")); + regOneTopDomain(qsl("sx")); + regOneTopDomain(qsl("sy")); + regOneTopDomain(qsl("sz")); + regOneTopDomain(qsl("tc")); + regOneTopDomain(qsl("td")); + regOneTopDomain(qsl("tf")); + regOneTopDomain(qsl("tg")); + regOneTopDomain(qsl("th")); + regOneTopDomain(qsl("tj")); + regOneTopDomain(qsl("tk")); + regOneTopDomain(qsl("tl")); + regOneTopDomain(qsl("tm")); + regOneTopDomain(qsl("tn")); + regOneTopDomain(qsl("to")); + regOneTopDomain(qsl("tp")); + regOneTopDomain(qsl("tr")); + regOneTopDomain(qsl("tt")); + regOneTopDomain(qsl("tv")); + regOneTopDomain(qsl("tw")); + regOneTopDomain(qsl("tz")); + regOneTopDomain(qsl("ua")); + regOneTopDomain(qsl("ug")); + regOneTopDomain(qsl("uk")); + regOneTopDomain(qsl("um")); + regOneTopDomain(qsl("us")); + regOneTopDomain(qsl("uy")); + regOneTopDomain(qsl("uz")); + regOneTopDomain(qsl("va")); + regOneTopDomain(qsl("vc")); + regOneTopDomain(qsl("ve")); + regOneTopDomain(qsl("vg")); + regOneTopDomain(qsl("vi")); + regOneTopDomain(qsl("vn")); + regOneTopDomain(qsl("vu")); + regOneTopDomain(qsl("wf")); + regOneTopDomain(qsl("ws")); + regOneTopDomain(qsl("ye")); + regOneTopDomain(qsl("yt")); + regOneTopDomain(qsl("yu")); + regOneTopDomain(qsl("za")); + regOneTopDomain(qsl("zm")); + regOneTopDomain(qsl("zw")); + regOneTopDomain(qsl("arpa")); + regOneTopDomain(qsl("aero")); + regOneTopDomain(qsl("asia")); + regOneTopDomain(qsl("biz")); + regOneTopDomain(qsl("cat")); + regOneTopDomain(qsl("com")); + regOneTopDomain(qsl("coop")); + regOneTopDomain(qsl("info")); + regOneTopDomain(qsl("int")); + regOneTopDomain(qsl("jobs")); + regOneTopDomain(qsl("mobi")); + regOneTopDomain(qsl("museum")); + regOneTopDomain(qsl("name")); + regOneTopDomain(qsl("net")); + regOneTopDomain(qsl("org")); + regOneTopDomain(qsl("post")); + regOneTopDomain(qsl("pro")); + regOneTopDomain(qsl("tel")); + regOneTopDomain(qsl("travel")); + regOneTopDomain(qsl("xxx")); + regOneTopDomain(qsl("edu")); + regOneTopDomain(qsl("gov")); + regOneTopDomain(qsl("mil")); + regOneTopDomain(qsl("local")); + regOneTopDomain(qsl("xn--lgbbat1ad8j")); + regOneTopDomain(qsl("xn--54b7fta0cc")); + regOneTopDomain(qsl("xn--fiqs8s")); + regOneTopDomain(qsl("xn--fiqz9s")); + regOneTopDomain(qsl("xn--wgbh1c")); + regOneTopDomain(qsl("xn--node")); + regOneTopDomain(qsl("xn--j6w193g")); + regOneTopDomain(qsl("xn--h2brj9c")); + regOneTopDomain(qsl("xn--mgbbh1a71e")); + regOneTopDomain(qsl("xn--fpcrj9c3d")); + regOneTopDomain(qsl("xn--gecrj9c")); + regOneTopDomain(qsl("xn--s9brj9c")); + regOneTopDomain(qsl("xn--xkc2dl3a5ee0h")); + regOneTopDomain(qsl("xn--45brj9c")); + regOneTopDomain(qsl("xn--mgba3a4f16a")); + regOneTopDomain(qsl("xn--mgbayh7gpa")); + regOneTopDomain(qsl("xn--80ao21a")); + regOneTopDomain(qsl("xn--mgbx4cd0ab")); + regOneTopDomain(qsl("xn--l1acc")); + regOneTopDomain(qsl("xn--mgbc0a9azcg")); + regOneTopDomain(qsl("xn--mgb9awbf")); + regOneTopDomain(qsl("xn--mgbai9azgqp6j")); + regOneTopDomain(qsl("xn--ygbi2ammx")); + regOneTopDomain(qsl("xn--wgbl6a")); + regOneTopDomain(qsl("xn--p1ai")); + regOneTopDomain(qsl("xn--mgberp4a5d4ar")); + regOneTopDomain(qsl("xn--90a3ac")); + regOneTopDomain(qsl("xn--yfro4i67o")); + regOneTopDomain(qsl("xn--clchc0ea0b2g2a9gcd")); + regOneTopDomain(qsl("xn--3e0b707e")); + regOneTopDomain(qsl("xn--fzc2c9e2c")); + regOneTopDomain(qsl("xn--xkc2al3hye2a")); + regOneTopDomain(qsl("xn--mgbtf8fl")); + regOneTopDomain(qsl("xn--kprw13d")); + regOneTopDomain(qsl("xn--kpry57d")); + regOneTopDomain(qsl("xn--o3cw4h")); + regOneTopDomain(qsl("xn--pgbs0dh")); + regOneTopDomain(qsl("xn--j1amh")); + regOneTopDomain(qsl("xn--mgbaam7a8h")); + regOneTopDomain(qsl("xn--mgb2ddes")); + regOneTopDomain(qsl("xn--ogbpf8fl")); + regOneTopDomain(QString::fromUtf8("\xd1\x80\xd1\x84")); +} + +namespace { +// accent char list taken from https://github.com/aristus/accent-folding +inline QChar chNoAccent(int32 code) { + switch (code) { + case 7834: return QChar(97); + case 193: return QChar(97); + case 225: return QChar(97); + case 192: return QChar(97); + case 224: return QChar(97); + case 258: return QChar(97); + case 259: return QChar(97); + case 7854: return QChar(97); + case 7855: return QChar(97); + case 7856: return QChar(97); + case 7857: return QChar(97); + case 7860: return QChar(97); + case 7861: return QChar(97); + case 7858: return QChar(97); + case 7859: return QChar(97); + case 194: return QChar(97); + case 226: return QChar(97); + case 7844: return QChar(97); + case 7845: return QChar(97); + case 7846: return QChar(97); + case 7847: return QChar(97); + case 7850: return QChar(97); + case 7851: return QChar(97); + case 7848: return QChar(97); + case 7849: return QChar(97); + case 461: return QChar(97); + case 462: return QChar(97); + case 197: return QChar(97); + case 229: return QChar(97); + case 506: return QChar(97); + case 507: return QChar(97); + case 196: return QChar(97); + case 228: return QChar(97); + case 478: return QChar(97); + case 479: return QChar(97); + case 195: return QChar(97); + case 227: return QChar(97); + case 550: return QChar(97); + case 551: return QChar(97); + case 480: return QChar(97); + case 481: return QChar(97); + case 260: return QChar(97); + case 261: return QChar(97); + case 256: return QChar(97); + case 257: return QChar(97); + case 7842: return QChar(97); + case 7843: return QChar(97); + case 512: return QChar(97); + case 513: return QChar(97); + case 514: return QChar(97); + case 515: return QChar(97); + case 7840: return QChar(97); + case 7841: return QChar(97); + case 7862: return QChar(97); + case 7863: return QChar(97); + case 7852: return QChar(97); + case 7853: return QChar(97); + case 7680: return QChar(97); + case 7681: return QChar(97); + case 570: return QChar(97); + case 11365: return QChar(97); + case 508: return QChar(97); + case 509: return QChar(97); + case 482: return QChar(97); + case 483: return QChar(97); + case 7682: return QChar(98); + case 7683: return QChar(98); + case 7684: return QChar(98); + case 7685: return QChar(98); + case 7686: return QChar(98); + case 7687: return QChar(98); + case 579: return QChar(98); + case 384: return QChar(98); + case 7532: return QChar(98); + case 385: return QChar(98); + case 595: return QChar(98); + case 386: return QChar(98); + case 387: return QChar(98); + case 262: return QChar(99); + case 263: return QChar(99); + case 264: return QChar(99); + case 265: return QChar(99); + case 268: return QChar(99); + case 269: return QChar(99); + case 266: return QChar(99); + case 267: return QChar(99); + case 199: return QChar(99); + case 231: return QChar(99); + case 7688: return QChar(99); + case 7689: return QChar(99); + case 571: return QChar(99); + case 572: return QChar(99); + case 391: return QChar(99); + case 392: return QChar(99); + case 597: return QChar(99); + case 270: return QChar(100); + case 271: return QChar(100); + case 7690: return QChar(100); + case 7691: return QChar(100); + case 7696: return QChar(100); + case 7697: return QChar(100); + case 7692: return QChar(100); + case 7693: return QChar(100); + case 7698: return QChar(100); + case 7699: return QChar(100); + case 7694: return QChar(100); + case 7695: return QChar(100); + case 272: return QChar(100); + case 273: return QChar(100); + case 7533: return QChar(100); + case 393: return QChar(100); + case 598: return QChar(100); + case 394: return QChar(100); + case 599: return QChar(100); + case 395: return QChar(100); + case 396: return QChar(100); + case 545: return QChar(100); + case 240: return QChar(100); + case 201: return QChar(101); + case 399: return QChar(101); + case 398: return QChar(101); + case 477: return QChar(101); + case 233: return QChar(101); + case 200: return QChar(101); + case 232: return QChar(101); + case 276: return QChar(101); + case 277: return QChar(101); + case 202: return QChar(101); + case 234: return QChar(101); + case 7870: return QChar(101); + case 7871: return QChar(101); + case 7872: return QChar(101); + case 7873: return QChar(101); + case 7876: return QChar(101); + case 7877: return QChar(101); + case 7874: return QChar(101); + case 7875: return QChar(101); + case 282: return QChar(101); + case 283: return QChar(101); + case 203: return QChar(101); + case 235: return QChar(101); + case 7868: return QChar(101); + case 7869: return QChar(101); + case 278: return QChar(101); + case 279: return QChar(101); + case 552: return QChar(101); + case 553: return QChar(101); + case 7708: return QChar(101); + case 7709: return QChar(101); + case 280: return QChar(101); + case 281: return QChar(101); + case 274: return QChar(101); + case 275: return QChar(101); + case 7702: return QChar(101); + case 7703: return QChar(101); + case 7700: return QChar(101); + case 7701: return QChar(101); + case 7866: return QChar(101); + case 7867: return QChar(101); + case 516: return QChar(101); + case 517: return QChar(101); + case 518: return QChar(101); + case 519: return QChar(101); + case 7864: return QChar(101); + case 7865: return QChar(101); + case 7878: return QChar(101); + case 7879: return QChar(101); + case 7704: return QChar(101); + case 7705: return QChar(101); + case 7706: return QChar(101); + case 7707: return QChar(101); + case 582: return QChar(101); + case 583: return QChar(101); + case 602: return QChar(101); + case 605: return QChar(101); + case 7710: return QChar(102); + case 7711: return QChar(102); + case 7534: return QChar(102); + case 401: return QChar(102); + case 402: return QChar(102); + case 500: return QChar(103); + case 501: return QChar(103); + case 286: return QChar(103); + case 287: return QChar(103); + case 284: return QChar(103); + case 285: return QChar(103); + case 486: return QChar(103); + case 487: return QChar(103); + case 288: return QChar(103); + case 289: return QChar(103); + case 290: return QChar(103); + case 291: return QChar(103); + case 7712: return QChar(103); + case 7713: return QChar(103); + case 484: return QChar(103); + case 485: return QChar(103); + case 403: return QChar(103); + case 608: return QChar(103); + case 292: return QChar(104); + case 293: return QChar(104); + case 542: return QChar(104); + case 543: return QChar(104); + case 7718: return QChar(104); + case 7719: return QChar(104); + case 7714: return QChar(104); + case 7715: return QChar(104); + case 7720: return QChar(104); + case 7721: return QChar(104); + case 7716: return QChar(104); + case 7717: return QChar(104); + case 7722: return QChar(104); + case 7723: return QChar(104); + case 817: return QChar(104); + case 7830: return QChar(104); + case 294: return QChar(104); + case 295: return QChar(104); + case 11367: return QChar(104); + case 11368: return QChar(104); + case 205: return QChar(105); + case 237: return QChar(105); + case 204: return QChar(105); + case 236: return QChar(105); + case 300: return QChar(105); + case 301: return QChar(105); + case 206: return QChar(105); + case 238: return QChar(105); + case 463: return QChar(105); + case 464: return QChar(105); + case 207: return QChar(105); + case 239: return QChar(105); + case 7726: return QChar(105); + case 7727: return QChar(105); + case 296: return QChar(105); + case 297: return QChar(105); + case 304: return QChar(105); + case 302: return QChar(105); + case 303: return QChar(105); + case 298: return QChar(105); + case 299: return QChar(105); + case 7880: return QChar(105); + case 7881: return QChar(105); + case 520: return QChar(105); + case 521: return QChar(105); + case 522: return QChar(105); + case 523: return QChar(105); + case 7882: return QChar(105); + case 7883: return QChar(105); + case 7724: return QChar(105); + case 7725: return QChar(105); + case 305: return QChar(105); + case 407: return QChar(105); + case 616: return QChar(105); + case 308: return QChar(106); + case 309: return QChar(106); + case 780: return QChar(106); + case 496: return QChar(106); + case 567: return QChar(106); + case 584: return QChar(106); + case 585: return QChar(106); + case 669: return QChar(106); + case 607: return QChar(106); + case 644: return QChar(106); + case 7728: return QChar(107); + case 7729: return QChar(107); + case 488: return QChar(107); + case 489: return QChar(107); + case 310: return QChar(107); + case 311: return QChar(107); + case 7730: return QChar(107); + case 7731: return QChar(107); + case 7732: return QChar(107); + case 7733: return QChar(107); + case 408: return QChar(107); + case 409: return QChar(107); + case 11369: return QChar(107); + case 11370: return QChar(107); + case 313: return QChar(97); + case 314: return QChar(108); + case 317: return QChar(108); + case 318: return QChar(108); + case 315: return QChar(108); + case 316: return QChar(108); + case 7734: return QChar(108); + case 7735: return QChar(108); + case 7736: return QChar(108); + case 7737: return QChar(108); + case 7740: return QChar(108); + case 7741: return QChar(108); + case 7738: return QChar(108); + case 7739: return QChar(108); + case 321: return QChar(108); + case 322: return QChar(108); + case 803: return QChar(108); + case 319: return QChar(108); + case 320: return QChar(108); + case 573: return QChar(108); + case 410: return QChar(108); + case 11360: return QChar(108); + case 11361: return QChar(108); + case 11362: return QChar(108); + case 619: return QChar(108); + case 620: return QChar(108); + case 621: return QChar(108); + case 564: return QChar(108); + case 7742: return QChar(109); + case 7743: return QChar(109); + case 7744: return QChar(109); + case 7745: return QChar(109); + case 7746: return QChar(109); + case 7747: return QChar(109); + case 625: return QChar(109); + case 323: return QChar(110); + case 324: return QChar(110); + case 504: return QChar(110); + case 505: return QChar(110); + case 327: return QChar(110); + case 328: return QChar(110); + case 209: return QChar(110); + case 241: return QChar(110); + case 7748: return QChar(110); + case 7749: return QChar(110); + case 325: return QChar(110); + case 326: return QChar(110); + case 7750: return QChar(110); + case 7751: return QChar(110); + case 7754: return QChar(110); + case 7755: return QChar(110); + case 7752: return QChar(110); + case 7753: return QChar(110); + case 413: return QChar(110); + case 626: return QChar(110); + case 544: return QChar(110); + case 414: return QChar(110); + case 627: return QChar(110); + case 565: return QChar(110); + case 776: return QChar(116); + case 211: return QChar(111); + case 243: return QChar(111); + case 210: return QChar(111); + case 242: return QChar(111); + case 334: return QChar(111); + case 335: return QChar(111); + case 212: return QChar(111); + case 244: return QChar(111); + case 7888: return QChar(111); + case 7889: return QChar(111); + case 7890: return QChar(111); + case 7891: return QChar(111); + case 7894: return QChar(111); + case 7895: return QChar(111); + case 7892: return QChar(111); + case 7893: return QChar(111); + case 465: return QChar(111); + case 466: return QChar(111); + case 214: return QChar(111); + case 246: return QChar(111); + case 554: return QChar(111); + case 555: return QChar(111); + case 336: return QChar(111); + case 337: return QChar(111); + case 213: return QChar(111); + case 245: return QChar(111); + case 7756: return QChar(111); + case 7757: return QChar(111); + case 7758: return QChar(111); + case 7759: return QChar(111); + case 556: return QChar(111); + case 557: return QChar(111); + case 558: return QChar(111); + case 559: return QChar(111); + case 560: return QChar(111); + case 561: return QChar(111); + case 216: return QChar(111); + case 248: return QChar(111); + case 510: return QChar(111); + case 511: return QChar(111); + case 490: return QChar(111); + case 491: return QChar(111); + case 492: return QChar(111); + case 493: return QChar(111); + case 332: return QChar(111); + case 333: return QChar(111); + case 7762: return QChar(111); + case 7763: return QChar(111); + case 7760: return QChar(111); + case 7761: return QChar(111); + case 7886: return QChar(111); + case 7887: return QChar(111); + case 524: return QChar(111); + case 525: return QChar(111); + case 526: return QChar(111); + case 527: return QChar(111); + case 416: return QChar(111); + case 417: return QChar(111); + case 7898: return QChar(111); + case 7899: return QChar(111); + case 7900: return QChar(111); + case 7901: return QChar(111); + case 7904: return QChar(111); + case 7905: return QChar(111); + case 7902: return QChar(111); + case 7903: return QChar(111); + case 7906: return QChar(111); + case 7907: return QChar(111); + case 7884: return QChar(111); + case 7885: return QChar(111); + case 7896: return QChar(111); + case 7897: return QChar(111); + case 415: return QChar(111); + case 629: return QChar(111); + case 7764: return QChar(112); + case 7765: return QChar(112); + case 7766: return QChar(112); + case 7767: return QChar(112); + case 11363: return QChar(112); + case 420: return QChar(112); + case 421: return QChar(112); + case 771: return QChar(112); + case 672: return QChar(113); + case 586: return QChar(113); + case 587: return QChar(113); + case 340: return QChar(114); + case 341: return QChar(114); + case 344: return QChar(114); + case 345: return QChar(114); + case 7768: return QChar(114); + case 7769: return QChar(114); + case 342: return QChar(114); + case 343: return QChar(114); + case 528: return QChar(114); + case 529: return QChar(114); + case 530: return QChar(114); + case 531: return QChar(114); + case 7770: return QChar(114); + case 7771: return QChar(114); + case 7772: return QChar(114); + case 7773: return QChar(114); + case 7774: return QChar(114); + case 7775: return QChar(114); + case 588: return QChar(114); + case 589: return QChar(114); + case 7538: return QChar(114); + case 636: return QChar(114); + case 11364: return QChar(114); + case 637: return QChar(114); + case 638: return QChar(114); + case 7539: return QChar(114); + case 223: return QChar(115); + case 346: return QChar(115); + case 347: return QChar(115); + case 7780: return QChar(115); + case 7781: return QChar(115); + case 348: return QChar(115); + case 349: return QChar(115); + case 352: return QChar(115); + case 353: return QChar(115); + case 7782: return QChar(115); + case 7783: return QChar(115); + case 7776: return QChar(115); + case 7777: return QChar(115); + case 7835: return QChar(115); + case 350: return QChar(115); + case 351: return QChar(115); + case 7778: return QChar(115); + case 7779: return QChar(115); + case 7784: return QChar(115); + case 7785: return QChar(115); + case 536: return QChar(115); + case 537: return QChar(115); + case 642: return QChar(115); + case 809: return QChar(115); + case 222: return QChar(116); + case 254: return QChar(116); + case 356: return QChar(116); + case 357: return QChar(116); + case 7831: return QChar(116); + case 7786: return QChar(116); + case 7787: return QChar(116); + case 354: return QChar(116); + case 355: return QChar(116); + case 7788: return QChar(116); + case 7789: return QChar(116); + case 538: return QChar(116); + case 539: return QChar(116); + case 7792: return QChar(116); + case 7793: return QChar(116); + case 7790: return QChar(116); + case 7791: return QChar(116); + case 358: return QChar(116); + case 359: return QChar(116); + case 574: return QChar(116); + case 11366: return QChar(116); + case 7541: return QChar(116); + case 427: return QChar(116); + case 428: return QChar(116); + case 429: return QChar(116); + case 430: return QChar(116); + case 648: return QChar(116); + case 566: return QChar(116); + case 218: return QChar(117); + case 250: return QChar(117); + case 217: return QChar(117); + case 249: return QChar(117); + case 364: return QChar(117); + case 365: return QChar(117); + case 219: return QChar(117); + case 251: return QChar(117); + case 467: return QChar(117); + case 468: return QChar(117); + case 366: return QChar(117); + case 367: return QChar(117); + case 220: return QChar(117); + case 252: return QChar(117); + case 471: return QChar(117); + case 472: return QChar(117); + case 475: return QChar(117); + case 476: return QChar(117); + case 473: return QChar(117); + case 474: return QChar(117); + case 469: return QChar(117); + case 470: return QChar(117); + case 368: return QChar(117); + case 369: return QChar(117); + case 360: return QChar(117); + case 361: return QChar(117); + case 7800: return QChar(117); + case 7801: return QChar(117); + case 370: return QChar(117); + case 371: return QChar(117); + case 362: return QChar(117); + case 363: return QChar(117); + case 7802: return QChar(117); + case 7803: return QChar(117); + case 7910: return QChar(117); + case 7911: return QChar(117); + case 532: return QChar(117); + case 533: return QChar(117); + case 534: return QChar(117); + case 535: return QChar(117); + case 431: return QChar(117); + case 432: return QChar(117); + case 7912: return QChar(117); + case 7913: return QChar(117); + case 7914: return QChar(117); + case 7915: return QChar(117); + case 7918: return QChar(117); + case 7919: return QChar(117); + case 7916: return QChar(117); + case 7917: return QChar(117); + case 7920: return QChar(117); + case 7921: return QChar(117); + case 7908: return QChar(117); + case 7909: return QChar(117); + case 7794: return QChar(117); + case 7795: return QChar(117); + case 7798: return QChar(117); + case 7799: return QChar(117); + case 7796: return QChar(117); + case 7797: return QChar(117); + case 580: return QChar(117); + case 649: return QChar(117); + case 7804: return QChar(118); + case 7805: return QChar(118); + case 7806: return QChar(118); + case 7807: return QChar(118); + case 434: return QChar(118); + case 651: return QChar(118); + case 7810: return QChar(119); + case 7811: return QChar(119); + case 7808: return QChar(119); + case 7809: return QChar(119); + case 372: return QChar(119); + case 373: return QChar(119); + case 778: return QChar(121); + case 7832: return QChar(119); + case 7812: return QChar(119); + case 7813: return QChar(119); + case 7814: return QChar(119); + case 7815: return QChar(119); + case 7816: return QChar(119); + case 7817: return QChar(119); + case 7820: return QChar(120); + case 7821: return QChar(120); + case 7818: return QChar(120); + case 7819: return QChar(120); + case 221: return QChar(121); + case 253: return QChar(121); + case 7922: return QChar(121); + case 7923: return QChar(121); + case 374: return QChar(121); + case 375: return QChar(121); + case 7833: return QChar(121); + case 376: return QChar(121); + case 255: return QChar(121); + case 7928: return QChar(121); + case 7929: return QChar(121); + case 7822: return QChar(121); + case 7823: return QChar(121); + case 562: return QChar(121); + case 563: return QChar(121); + case 7926: return QChar(121); + case 7927: return QChar(121); + case 7924: return QChar(121); + case 7925: return QChar(121); + case 655: return QChar(121); + case 590: return QChar(121); + case 591: return QChar(121); + case 435: return QChar(121); + case 436: return QChar(121); + case 377: return QChar(122); + case 378: return QChar(122); + case 7824: return QChar(122); + case 7825: return QChar(122); + case 381: return QChar(122); + case 382: return QChar(122); + case 379: return QChar(122); + case 380: return QChar(122); + case 7826: return QChar(122); + case 7827: return QChar(122); + case 7828: return QChar(122); + case 7829: return QChar(122); + case 437: return QChar(122); + case 438: return QChar(122); + case 548: return QChar(122); + case 549: return QChar(122); + case 656: return QChar(122); + case 657: return QChar(122); + case 11371: return QChar(122); + case 11372: return QChar(122); + case 494: return QChar(122); + case 495: return QChar(122); + case 442: return QChar(122); + case 65298: return QChar(50); + case 65302: return QChar(54); + case 65314: return QChar(66); + case 65318: return QChar(70); + case 65322: return QChar(74); + case 65326: return QChar(78); + case 65330: return QChar(82); + case 65334: return QChar(86); + case 65338: return QChar(90); + case 65346: return QChar(98); + case 65350: return QChar(102); + case 65354: return QChar(106); + case 65358: return QChar(110); + case 65362: return QChar(114); + case 65366: return QChar(118); + case 65370: return QChar(122); + case 65297: return QChar(49); + case 65301: return QChar(53); + case 65305: return QChar(57); + case 65313: return QChar(65); + case 65317: return QChar(69); + case 65321: return QChar(73); + case 65325: return QChar(77); + case 65329: return QChar(81); + case 65333: return QChar(85); + case 65337: return QChar(89); + case 65345: return QChar(97); + case 65349: return QChar(101); + case 65353: return QChar(105); + case 65357: return QChar(109); + case 65361: return QChar(113); + case 65365: return QChar(117); + case 65369: return QChar(121); + case 65296: return QChar(48); + case 65300: return QChar(52); + case 65304: return QChar(56); + case 65316: return QChar(68); + case 65320: return QChar(72); + case 65324: return QChar(76); + case 65328: return QChar(80); + case 65332: return QChar(84); + case 65336: return QChar(88); + case 65348: return QChar(100); + case 65352: return QChar(104); + case 65356: return QChar(108); + case 65360: return QChar(112); + case 65364: return QChar(116); + case 65368: return QChar(120); + case 65299: return QChar(51); + case 65303: return QChar(55); + case 65315: return QChar(67); + case 65319: return QChar(71); + case 65323: return QChar(75); + case 65327: return QChar(79); + case 65331: return QChar(83); + case 65335: return QChar(87); + case 65347: return QChar(99); + case 65351: return QChar(103); + case 65355: return QChar(107); + case 65359: return QChar(111); + case 65363: return QChar(115); + case 65367: return QChar(119); + case 1105: return QChar(1077); + default: + break; + } + return QChar(0); +} +} + +QString textClean(const QString &text) { + QString result(text); + for (const QChar *s = text.unicode(), *ch = s, *e = text.unicode() + text.size(); ch != e; ++ch) { + if (*ch == TextCommand) { + result[int(ch - s)] = QChar::Space; + } + } + return result; +} + +QString textRichPrepare(const QString &text) { + QString result; + result.reserve(text.size()); + const QChar *s = text.constData(), *ch = s; + for (const QChar *e = s + text.size(); ch != e; ++ch) { + if (*ch == TextCommand) { + if (ch > s) result.append(s, ch - s); + result.append(QChar::Space); + s = ch + 1; + continue; + } + if (ch->unicode() == '\\' || ch->unicode() == '[') { + if (ch > s) result.append(s, ch - s); + result.append('\\'); + s = ch; + continue; + } + } + if (ch > s) result.append(s, ch - s); + return result; +} + +QString textOneLine(const QString &text, bool trim, bool rich) { + QString result(text); + const QChar *s = text.unicode(), *ch = s, *e = text.unicode() + text.size(); + if (trim) { + while (s < e && chIsTrimmed(*s)) { + ++s; + } + while (s < e && chIsTrimmed(*(e - 1))) { + --e; + } + if (e - s != text.size()) { + result = text.mid(s - text.unicode(), e - s); + } + } + for (const QChar *ch = s; ch != e; ++ch) { + if (chIsNewline(*ch)) { + result[int(ch - s)] = QChar::Space; + } + } + return result; +} + +QString textAccentFold(const QString &text) { + QString result(text); + bool copying = false; + int32 i = 0; + for (const QChar *s = text.unicode(), *ch = s, *e = text.unicode() + text.size(); ch != e; ++ch, ++i) { + if (ch->unicode() < 128) { + if (copying) result[i] = *ch; + continue; + } + if (chIsDiac(*ch)) { + copying = true; + --i; + continue; + } + if (ch->isHighSurrogate() && ch + 1 < e && (ch + 1)->isLowSurrogate()) { + QChar noAccent = chNoAccent(QChar::surrogateToUcs4(*ch, *(ch + 1))); + if (noAccent.unicode() > 0) { + copying = true; + result[i] = noAccent; + } else { + if (copying) result[i] = *ch; + ++ch, ++i; + if (copying) result[i] = *ch; + } + } else { + QChar noAccent = chNoAccent(ch->unicode()); + if (noAccent.unicode() > 0 && noAccent != *ch) { + result[i] = noAccent; + } else if (copying) { + result[i] = *ch; + } + } + } + return (i < result.size()) ? result.mid(0, i) : result; +} + +QString textSearchKey(const QString &text) { + return textAccentFold(text.trimmed().toLower()); +} + +bool textSplit(QString &sendingText, EntitiesInText &sendingEntities, QString &leftText, EntitiesInText &leftEntities, int32 limit) { + if (leftText.isEmpty() || !limit) return false; + + int32 currentEntity = 0, goodEntity = currentEntity, entityCount = leftEntities.size(); + bool goodInEntity = false, goodCanBreakEntity = false; + + int32 s = 0, half = limit / 2, goodLevel = 0; + for (const QChar *start = leftText.constData(), *ch = start, *end = leftText.constEnd(), *good = ch; ch != end; ++ch, ++s) { + while (currentEntity < entityCount && ch >= start + leftEntities[currentEntity].offset + leftEntities[currentEntity].length) { + ++currentEntity; + } + +#define MARK_GOOD_AS_LEVEL(level) \ +if (goodLevel <= (level)) {\ +goodLevel = (level);\ +good = ch;\ +goodEntity = currentEntity;\ +goodInEntity = inEntity;\ +goodCanBreakEntity = canBreakEntity;\ +} + + if (s > half) { + bool inEntity = (currentEntity < entityCount) && (ch > start + leftEntities[currentEntity].offset) && (ch < start + leftEntities[currentEntity].offset + leftEntities[currentEntity].length); + EntityInTextType entityType = (currentEntity < entityCount) ? leftEntities[currentEntity].type : EntityInTextBold; + bool canBreakEntity = (entityType == EntityInTextPre || entityType == EntityInTextCode); + int32 noEntityLevel = inEntity ? 0 : 1; + if (inEntity && !canBreakEntity) { + MARK_GOOD_AS_LEVEL(0); + } else { + if (chIsNewline(*ch)) { + if (inEntity) { + if (ch + 1 < end && chIsNewline(*(ch + 1))) { + MARK_GOOD_AS_LEVEL(12); + } else { + MARK_GOOD_AS_LEVEL(11); + } + } else if (ch + 1 < end && chIsNewline(*(ch + 1))) { + MARK_GOOD_AS_LEVEL(15); + } else if (currentEntity < entityCount && ch + 1 == start + leftEntities[currentEntity].offset && leftEntities[currentEntity].type == EntityInTextPre) { + MARK_GOOD_AS_LEVEL(14); + } else if (currentEntity > 0 && ch == start + leftEntities[currentEntity - 1].offset + leftEntities[currentEntity - 1].length && leftEntities[currentEntity - 1].type == EntityInTextPre) { + MARK_GOOD_AS_LEVEL(14); + } else { + MARK_GOOD_AS_LEVEL(13); + } + } else if (chIsSpace(*ch)) { + if (chIsSentenceEnd(*(ch - 1))) { + MARK_GOOD_AS_LEVEL(9 + noEntityLevel); + } else if (chIsSentencePartEnd(*(ch - 1))) { + MARK_GOOD_AS_LEVEL(7 + noEntityLevel); + } else { + MARK_GOOD_AS_LEVEL(5 + noEntityLevel); + } + } else if (chIsWordSeparator(*(ch - 1))) { + MARK_GOOD_AS_LEVEL(3 + noEntityLevel); + } else { + MARK_GOOD_AS_LEVEL(1 + noEntityLevel); + } + } + } + +#undef MARK_GOOD_AS_LEVEL + + int elen = 0; + if (EmojiPtr e = emojiFromText(ch, end, &elen)) { + for (int i = 0; i < elen; ++i, ++ch, ++s) { + if (ch->isHighSurrogate() && i + 1 < elen && (ch + 1)->isLowSurrogate()) { + ++ch; + ++i; + } + } + --ch; + --s; + } else if (ch->isHighSurrogate() && ch + 1 < end && (ch + 1)->isLowSurrogate()) { + ++ch; + } + if (s >= limit) { + sendingText = leftText.mid(0, good - start); + leftText = leftText.mid(good - start); + if (goodInEntity) { + if (goodCanBreakEntity) { + sendingEntities = leftEntities.mid(0, goodEntity + 1); + sendingEntities.back().length = good - start - sendingEntities.back().offset; + leftEntities = leftEntities.mid(goodEntity); + for (EntitiesInText::iterator i = leftEntities.begin(), e = leftEntities.end(); i != e; ++i) { + i->offset -= good - start; + if (i->offset < 0) { + i->length += i->offset; + i->offset = 0; + } + } + } else { + sendingEntities = leftEntities.mid(0, goodEntity); + leftEntities = leftEntities.mid(goodEntity + 1); + } + } else { + sendingEntities = leftEntities.mid(0, goodEntity); + leftEntities = leftEntities.mid(goodEntity); + for (EntitiesInText::iterator i = leftEntities.begin(), e = leftEntities.end(); i != e; ++i) { + i->offset -= good - start; + } + } + return true; + } + } + sendingText = leftText; + leftText = QString(); + sendingEntities = leftEntities; + leftEntities = EntitiesInText(); + return true; +} + +bool textcmdStartsLink(const QChar *start, int32 len, int32 commandOffset) { + if (commandOffset + 2 < len) { + if (*(start + commandOffset + 1) == TextCommandLinkIndex) { + return (*(start + commandOffset + 2) != 0); + } + return (*(start + commandOffset + 1) != TextCommandLinkText); + } + return false; +} + +bool checkTagStartInCommand(const QChar *start, int32 len, int32 tagStart, int32 &commandOffset, bool &commandIsLink, bool &inLink) { + bool inCommand = false; + const QChar *commandEnd = start + commandOffset; + while (commandOffset < len && tagStart > commandOffset) { // skip commands, evaluating are we in link or not + commandEnd = textSkipCommand(start + commandOffset, start + len); + if (commandEnd > start + commandOffset) { + if (tagStart < (commandEnd - start)) { + inCommand = true; + break; + } + for (commandOffset = commandEnd - start; commandOffset < len; ++commandOffset) { + if (*(start + commandOffset) == TextCommand) { + inLink = commandIsLink; + commandIsLink = textcmdStartsLink(start, len, commandOffset); + break; + } + } + if (commandOffset >= len) { + inLink = commandIsLink; + commandIsLink = false; + } + } else { + break; + } + } + if (inCommand) { + commandOffset = commandEnd - start; + } + return inCommand; +} + +EntitiesInText entitiesFromMTP(const QVector &entities) { + EntitiesInText result; + if (!entities.isEmpty()) { + result.reserve(entities.size()); + for_const (const auto &entity, entities) { + switch (entity.type()) { + case mtpc_messageEntityUrl: { const auto &d(entity.c_messageEntityUrl()); result.push_back(EntityInText(EntityInTextUrl, d.voffset.v, d.vlength.v)); } break; + case mtpc_messageEntityTextUrl: { const auto &d(entity.c_messageEntityTextUrl()); result.push_back(EntityInText(EntityInTextCustomUrl, d.voffset.v, d.vlength.v, textClean(qs(d.vurl)))); } break; + case mtpc_messageEntityEmail: { const auto &d(entity.c_messageEntityEmail()); result.push_back(EntityInText(EntityInTextEmail, d.voffset.v, d.vlength.v)); } break; + case mtpc_messageEntityHashtag: { const auto &d(entity.c_messageEntityHashtag()); result.push_back(EntityInText(EntityInTextHashtag, d.voffset.v, d.vlength.v)); } break; + case mtpc_messageEntityMention: { const auto &d(entity.c_messageEntityMention()); result.push_back(EntityInText(EntityInTextMention, d.voffset.v, d.vlength.v)); } break; + case mtpc_messageEntityBotCommand: { const auto &d(entity.c_messageEntityBotCommand()); result.push_back(EntityInText(EntityInTextBotCommand, d.voffset.v, d.vlength.v)); } break; + case mtpc_messageEntityBold: { const auto &d(entity.c_messageEntityBold()); result.push_back(EntityInText(EntityInTextBold, d.voffset.v, d.vlength.v)); } break; + case mtpc_messageEntityItalic: { const auto &d(entity.c_messageEntityItalic()); result.push_back(EntityInText(EntityInTextItalic, d.voffset.v, d.vlength.v)); } break; + case mtpc_messageEntityCode: { const auto &d(entity.c_messageEntityCode()); result.push_back(EntityInText(EntityInTextCode, d.voffset.v, d.vlength.v)); } break; + case mtpc_messageEntityPre: { const auto &d(entity.c_messageEntityPre()); result.push_back(EntityInText(EntityInTextPre, d.voffset.v, d.vlength.v, textClean(qs(d.vlanguage)))); } break; + } + } + } + return result; +} + +MTPVector linksToMTP(const EntitiesInText &links, bool sending) { + MTPVector result(MTP_vector(0)); + auto &v = result._vector().v; + for_const (const auto &link, links) { + if (link.length <= 0) continue; + if (sending && link.type != EntityInTextCode && link.type != EntityInTextPre) continue; + + auto offset = MTP_int(link.offset), length = MTP_int(link.length); + switch (link.type) { + case EntityInTextUrl: v.push_back(MTP_messageEntityUrl(offset, length)); break; + case EntityInTextCustomUrl: v.push_back(MTP_messageEntityTextUrl(offset, length, MTP_string(link.text))); break; + case EntityInTextEmail: v.push_back(MTP_messageEntityEmail(offset, length)); break; + case EntityInTextHashtag: v.push_back(MTP_messageEntityHashtag(offset, length)); break; + case EntityInTextMention: v.push_back(MTP_messageEntityMention(offset, length)); break; + case EntityInTextBotCommand: v.push_back(MTP_messageEntityBotCommand(offset, length)); break; + case EntityInTextBold: v.push_back(MTP_messageEntityBold(offset, length)); break; + case EntityInTextItalic: v.push_back(MTP_messageEntityItalic(offset, length)); break; + case EntityInTextCode: v.push_back(MTP_messageEntityCode(offset, length)); break; + case EntityInTextPre: v.push_back(MTP_messageEntityPre(offset, length, MTP_string(link.text))); break; + } + } + return result; +} + +EntitiesInText textParseEntities(QString &text, int32 flags, bool rich) { // some code is duplicated in flattextarea.cpp! + EntitiesInText result, mono; + + bool withHashtags = (flags & TextParseHashtags); + bool withMentions = (flags & TextParseMentions); + bool withBotCommands = (flags & TextParseBotCommands); + bool withMono = (flags & TextParseMono); + + if (withMono) { // parse mono entities (code and pre) + QString newText; + + int32 offset = 0, matchOffset = offset, len = text.size(), commandOffset = rich ? 0 : len; + bool inLink = false, commandIsLink = false; + const QChar *start = text.constData(); + for (; matchOffset < len;) { + if (commandOffset <= matchOffset) { + for (commandOffset = matchOffset; commandOffset < len; ++commandOffset) { + if (*(start + commandOffset) == TextCommand) { + inLink = commandIsLink; + commandIsLink = textcmdStartsLink(start, len, commandOffset); + break; + } + } + if (commandOffset >= len) { + inLink = commandIsLink; + commandIsLink = false; + } + } + auto mPre = _rePre.match(text, matchOffset); + auto mCode = _reCode.match(text, matchOffset); + if (!mPre.hasMatch() && !mCode.hasMatch()) break; + + int32 preStart = mPre.hasMatch() ? mPre.capturedStart() : INT_MAX, + preEnd = mPre.hasMatch() ? mPre.capturedEnd() : INT_MAX, + codeStart = mCode.hasMatch() ? mCode.capturedStart() : INT_MAX, + codeEnd = mCode.hasMatch() ? mCode.capturedEnd() : INT_MAX, + tagStart, tagEnd; + if (mPre.hasMatch()) { + if (!mPre.capturedRef(1).isEmpty()) { + ++preStart; + } + if (!mPre.capturedRef(4).isEmpty()) { + --preEnd; + } + } + if (mCode.hasMatch()) { + if (!mCode.capturedRef(1).isEmpty()) { + ++codeStart; + } + if (!mCode.capturedRef(4).isEmpty()) { + --codeEnd; + } + } + + bool pre = (preStart <= codeStart); + auto mTag = pre ? mPre : mCode; + if (pre) { + tagStart = preStart; + tagEnd = preEnd; + } else { + tagStart = codeStart; + tagEnd = codeEnd; + } + + bool inCommand = checkTagStartInCommand(start, len, tagStart, commandOffset, commandIsLink, inLink); + if (inCommand || inLink) { + matchOffset = commandOffset; + continue; + } + + if (newText.isEmpty()) newText.reserve(text.size()); + + bool addNewlineBefore = false, addNewlineAfter = false; + int32 outerStart = tagStart, outerEnd = tagEnd; + int32 innerStart = tagStart + mTag.capturedLength(2), innerEnd = tagEnd - mTag.capturedLength(3); + if (pre) { + while (outerStart > 0 && chIsSpace(*(start + outerStart - 1), rich) && !chIsNewline(*(start + outerStart - 1))) { + --outerStart; + } + addNewlineBefore = (outerStart > 0 && !chIsNewline(*(start + outerStart - 1))); + + for (int32 testInnerStart = innerStart; testInnerStart < innerEnd; ++testInnerStart) { + if (chIsNewline(*(start + testInnerStart))) { + innerStart = testInnerStart + 1; + break; + } else if (!chIsSpace(*(start + testInnerStart))) { + break; + } + } + for (int32 testInnerEnd = innerEnd; innerStart < testInnerEnd;) { + --testInnerEnd; + if (chIsNewline(*(start + testInnerEnd))) { + innerEnd = testInnerEnd; + break; + } else if (!chIsSpace(*(start + testInnerEnd))) { + break; + } + } + + while (outerEnd < len && chIsSpace(*(start + outerEnd)) && !chIsNewline(*(start + outerEnd))) { + ++outerEnd; + } + addNewlineAfter = (outerEnd < len && !chIsNewline(*(start + outerEnd))); + } + if (outerStart > offset) newText.append(start + offset, outerStart - offset); + if (addNewlineBefore) newText.append('\n'); + + int32 tagLength = innerEnd - innerStart; + mono.push_back(EntityInText(pre ? EntityInTextPre : EntityInTextCode, newText.size(), tagLength)); + + newText.append(start + innerStart, tagLength); + if (addNewlineAfter) newText.append('\n'); + + offset = matchOffset = outerEnd; + } + if (!newText.isEmpty()) { + newText.append(start + offset, len - offset); + text = newText; + } + } + int32 monoEntity = 0, monoCount = mono.size(), monoTill = 0; + + initLinkSets(); + int32 len = text.size(), commandOffset = rich ? 0 : len; + bool inLink = false, commandIsLink = false; + const QChar *start = text.constData(), *end = start + text.size(); + for (int32 offset = 0, matchOffset = offset, mentionSkip = 0; offset < len;) { + if (commandOffset <= offset) { + for (commandOffset = offset; commandOffset < len; ++commandOffset) { + if (*(start + commandOffset) == TextCommand) { + inLink = commandIsLink; + commandIsLink = textcmdStartsLink(start, len, commandOffset); + break; + } + } + } + QRegularExpressionMatch mDomain = _reDomain.match(text, matchOffset); + QRegularExpressionMatch mExplicitDomain = _reExplicitDomain.match(text, matchOffset); + QRegularExpressionMatch mHashtag = withHashtags ? _reHashtag.match(text, matchOffset) : QRegularExpressionMatch(); + QRegularExpressionMatch mMention = withMentions ? _reMention.match(text, qMax(mentionSkip, matchOffset)) : QRegularExpressionMatch(); + QRegularExpressionMatch mBotCommand = withBotCommands ? _reBotCommand.match(text, matchOffset) : QRegularExpressionMatch(); + + EntityInTextType lnkType = EntityInTextUrl; + int32 lnkStart = 0, lnkLength = 0; + int32 domainStart = mDomain.hasMatch() ? mDomain.capturedStart() : INT_MAX, + domainEnd = mDomain.hasMatch() ? mDomain.capturedEnd() : INT_MAX, + explicitDomainStart = mExplicitDomain.hasMatch() ? mExplicitDomain.capturedStart() : INT_MAX, + explicitDomainEnd = mExplicitDomain.hasMatch() ? mExplicitDomain.capturedEnd() : INT_MAX, + hashtagStart = mHashtag.hasMatch() ? mHashtag.capturedStart() : INT_MAX, + hashtagEnd = mHashtag.hasMatch() ? mHashtag.capturedEnd() : INT_MAX, + mentionStart = mMention.hasMatch() ? mMention.capturedStart() : INT_MAX, + mentionEnd = mMention.hasMatch() ? mMention.capturedEnd() : INT_MAX, + botCommandStart = mBotCommand.hasMatch() ? mBotCommand.capturedStart() : INT_MAX, + botCommandEnd = mBotCommand.hasMatch() ? mBotCommand.capturedEnd() : INT_MAX; + if (mHashtag.hasMatch()) { + if (!mHashtag.capturedRef(1).isEmpty()) { + ++hashtagStart; + } + if (!mHashtag.capturedRef(2).isEmpty()) { + --hashtagEnd; + } + } + while (mMention.hasMatch()) { + if (!mMention.capturedRef(1).isEmpty()) { + ++mentionStart; + } + if (!mMention.capturedRef(2).isEmpty()) { + --mentionEnd; + } + if (!(start + mentionStart + 1)->isLetter() || !(start + mentionEnd - 1)->isLetterOrNumber()) { + mentionSkip = mentionEnd; + mMention = _reMention.match(text, qMax(mentionSkip, matchOffset)); + if (mMention.hasMatch()) { + mentionStart = mMention.capturedStart(); + mentionEnd = mMention.capturedEnd(); + } else { + mentionStart = INT_MAX; + mentionEnd = INT_MAX; + } + } else { + break; + } + } + if (mBotCommand.hasMatch()) { + if (!mBotCommand.capturedRef(1).isEmpty()) { + ++botCommandStart; + } + if (!mBotCommand.capturedRef(3).isEmpty()) { + --botCommandEnd; + } + } + if (!mDomain.hasMatch() && !mExplicitDomain.hasMatch() && !mHashtag.hasMatch() && !mMention.hasMatch() && !mBotCommand.hasMatch()) { + break; + } + + if (explicitDomainStart < domainStart) { + domainStart = explicitDomainStart; + domainEnd = explicitDomainEnd; + mDomain = mExplicitDomain; + } + if (mentionStart < hashtagStart && mentionStart < domainStart && mentionStart < botCommandStart) { + bool inCommand = checkTagStartInCommand(start, len, mentionStart, commandOffset, commandIsLink, inLink); + if (inCommand || inLink) { + offset = matchOffset = commandOffset; + continue; + } + + lnkType = EntityInTextMention; + lnkStart = mentionStart; + lnkLength = mentionEnd - mentionStart; + } else if (hashtagStart < domainStart && hashtagStart < botCommandStart) { + bool inCommand = checkTagStartInCommand(start, len, hashtagStart, commandOffset, commandIsLink, inLink); + if (inCommand || inLink) { + offset = matchOffset = commandOffset; + continue; + } + + lnkType = EntityInTextHashtag; + lnkStart = hashtagStart; + lnkLength = hashtagEnd - hashtagStart; + } else if (botCommandStart < domainStart) { + bool inCommand = checkTagStartInCommand(start, len, botCommandStart, commandOffset, commandIsLink, inLink); + if (inCommand || inLink) { + offset = matchOffset = commandOffset; + continue; + } + + lnkType = EntityInTextBotCommand; + lnkStart = botCommandStart; + lnkLength = botCommandEnd - botCommandStart; + } else { + bool inCommand = checkTagStartInCommand(start, len, domainStart, commandOffset, commandIsLink, inLink); + if (inCommand || inLink) { + offset = matchOffset = commandOffset; + continue; + } + + QString protocol = mDomain.captured(1).toLower(); + QString topDomain = mDomain.captured(3).toLower(); + + bool isProtocolValid = protocol.isEmpty() || _validProtocols.contains(hashCrc32(protocol.constData(), protocol.size() * sizeof(QChar))); + bool isTopDomainValid = !protocol.isEmpty() || _validTopDomains.contains(hashCrc32(topDomain.constData(), topDomain.size() * sizeof(QChar))); + + if (protocol.isEmpty() && domainStart > offset + 1 && *(start + domainStart - 1) == QChar('@')) { + QString forMailName = text.mid(offset, domainStart - offset - 1); + QRegularExpressionMatch mMailName = _reMailName.match(forMailName); + if (mMailName.hasMatch()) { + int32 mailStart = offset + mMailName.capturedStart(); + if (mailStart < offset) { + mailStart = offset; + } + lnkType = EntityInTextEmail; + lnkStart = mailStart; + lnkLength = domainEnd - mailStart; + } + } + if (lnkType == EntityInTextUrl && !lnkLength) { + if (!isProtocolValid || !isTopDomainValid) { + matchOffset = domainEnd; + continue; + } + lnkStart = domainStart; + + QStack parenth; + const QChar *domainEnd = start + mDomain.capturedEnd(), *p = domainEnd; + for (; p < end; ++p) { + QChar ch(*p); + if (chIsLinkEnd(ch)) break; // link finished + if (chIsAlmostLinkEnd(ch)) { + const QChar *endTest = p + 1; + while (endTest < end && chIsAlmostLinkEnd(*endTest)) { + ++endTest; + } + if (endTest >= end || chIsLinkEnd(*endTest)) { + break; // link finished at p + } + p = endTest; + ch = *p; + } + if (ch == '(' || ch == '[' || ch == '{' || ch == '<') { + parenth.push(p); + } else if (ch == ')' || ch == ']' || ch == '}' || ch == '>') { + if (parenth.isEmpty()) break; + const QChar *q = parenth.pop(), open(*q); + if ((ch == ')' && open != '(') || (ch == ']' && open != '[') || (ch == '}' && open != '{') || (ch == '>' && open != '<')) { + p = q; + break; + } + } + } + if (p > domainEnd) { // check, that domain ended + if (domainEnd->unicode() != '/' && domainEnd->unicode() != '?') { + matchOffset = domainEnd - start; + continue; + } + } + lnkLength = (p - start) - lnkStart; + } + } + for (; monoEntity < monoCount && mono[monoEntity].offset <= lnkStart; ++monoEntity) { + monoTill = qMax(monoTill, mono[monoEntity].offset + mono[monoEntity].length); + result.push_back(mono[monoEntity]); + } + if (lnkStart >= monoTill) { + result.push_back(EntityInText(lnkType, lnkStart, lnkLength)); + } + + offset = matchOffset = lnkStart + lnkLength; + } + for (; monoEntity < monoCount; ++monoEntity) { + monoTill = qMax(monoTill, mono[monoEntity].offset + mono[monoEntity].length); + result.push_back(mono[monoEntity]); + } + + return result; +} + +QString textApplyEntities(const QString &text, const EntitiesInText &entities) { + if (entities.isEmpty()) return text; + + QMultiMap closingTags; + QString code(qsl("`")), pre(qsl("```")); + + QString result; + int32 size = text.size(); + const QChar *b = text.constData(), *already = b, *e = b + size; + EntitiesInText::const_iterator entity = entities.cbegin(), end = entities.cend(); + while (entity != end && ((entity->type != EntityInTextCode && entity->type != EntityInTextPre) || entity->length <= 0 || entity->offset >= size)) { + ++entity; + } + while (entity != end || !closingTags.isEmpty()) { + int32 nextOpenEntity = (entity == end) ? (size + 1) : entity->offset; + int32 nextCloseEntity = closingTags.isEmpty() ? (size + 1) : closingTags.cbegin().key(); + if (nextOpenEntity <= nextCloseEntity) { + QString tag = (entity->type == EntityInTextCode) ? code : pre; + if (result.isEmpty()) result.reserve(text.size() + entities.size() * pre.size() * 2); + + const QChar *offset = b + nextOpenEntity; + if (offset > already) { + result.append(already, offset - already); + already = offset; + } + result.append(tag); + closingTags.insert(qMin(entity->offset + entity->length, size), tag); + + ++entity; + while (entity != end && ((entity->type != EntityInTextCode && entity->type != EntityInTextPre) || entity->length <= 0 || entity->offset >= size)) { + ++entity; + } + } else { + const QChar *offset = b + nextCloseEntity; + if (offset > already) { + result.append(already, offset - already); + already = offset; + } + result.append(closingTags.cbegin().value()); + closingTags.erase(closingTags.begin()); + } + } + if (result.isEmpty()) { + return text; + } + const QChar *offset = b + size; + if (offset > already) { + result.append(already, offset - already); + } + return result; +} + +void replaceStringWithEntities(const QLatin1String &from, QChar to, QString &result, EntitiesInText &entities, bool checkSpace = false) { + int32 len = from.size(), s = result.size(), offset = 0, length = 0; + EntitiesInText::iterator i = entities.begin(), e = entities.end(); + for (QChar *start = result.data(); offset < s;) { + int32 nextOffset = result.indexOf(from, offset); + if (nextOffset < 0) { + moveStringPart(start, length, offset, s - offset, entities); + break; + } + + if (checkSpace) { + bool spaceBefore = (nextOffset > 0) && (start + nextOffset - 1)->isSpace(); + bool spaceAfter = (nextOffset + len < s) && (start + nextOffset + len)->isSpace(); + if (!spaceBefore && !spaceAfter) { + moveStringPart(start, length, offset, nextOffset - offset + len + 1, entities); + continue; + } + } + + bool skip = false; + for (; i != e; ++i) { // find and check next finishing entity + if (i->offset + i->length > nextOffset) { + skip = (i->offset < nextOffset + len); + break; + } + } + if (skip) { + moveStringPart(start, length, offset, nextOffset - offset + len, entities); + continue; + } + + moveStringPart(start, length, offset, nextOffset - offset, entities); + + *(start + length) = to; + ++length; + offset += len; + } + if (length < s) result.resize(length); +} + +QString prepareTextWithEntities(QString result, EntitiesInText &entities, int32 flags) { + cleanTextWithEntities(result, entities); + + if (flags) { + entities = textParseEntities(result, flags); + } + + replaceStringWithEntities(qstr("--"), QChar(8212), result, entities, true); + replaceStringWithEntities(qstr("<<"), QChar(171), result, entities); + replaceStringWithEntities(qstr(">>"), QChar(187), result, entities); + + if (cReplaceEmojis()) { + result = replaceEmojis(result, entities); + } + + trimTextWithEntities(result, entities); + + return result; +} + +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 (auto &entity : entities) { + if (entity.offset >= from + count) break; + if (entity.offset + entity.length < from) continue; + if (entity.offset >= from) { + entity.offset -= (from - to); + entity.length += (from - to); + } + if (entity.offset + entity.length < from + count) { + entity.length -= (from - to); + } + } + } + to += count; + from += count; + } +} + +// replace bad symbols with space and remove \r +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); +} + +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; + } + } +} diff --git a/Telegram/SourceFiles/ui/text/text_entity.h b/Telegram/SourceFiles/ui/text/text_entity.h new file mode 100644 index 000000000..aaf9406f6 --- /dev/null +++ b/Telegram/SourceFiles/ui/text/text_entity.h @@ -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 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 &entities); +MTPVector 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); \ No newline at end of file diff --git a/Telegram/SourceFiles/ui/toast/toast_widget.h b/Telegram/SourceFiles/ui/toast/toast_widget.h index 4ee46f599..1d255e2c1 100644 --- a/Telegram/SourceFiles/ui/toast/toast_widget.h +++ b/Telegram/SourceFiles/ui/toast/toast_widget.h @@ -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 { diff --git a/Telegram/Telegram.pro b/Telegram/Telegram.pro index ff1580fb4..b33c46f6f 100644 --- a/Telegram/Telegram.pro +++ b/Telegram/Telegram.pro @@ -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 \ diff --git a/Telegram/Telegram.vcxproj b/Telegram/Telegram.vcxproj index 1fb2d8faf..dc2676a0b 100644 --- a/Telegram/Telegram.vcxproj +++ b/Telegram/Telegram.vcxproj @@ -1170,7 +1170,9 @@ - + + + @@ -1509,7 +1511,6 @@ - $(QTDIR)\bin\moc.exe;%(FullPath) Moc%27ing twidget.h... @@ -1524,6 +1525,9 @@ .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp "$(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" + + + $(QTDIR)\bin\moc.exe;%(FullPath) diff --git a/Telegram/Telegram.vcxproj.filters b/Telegram/Telegram.vcxproj.filters index 86772f8db..0b17e3c99 100644 --- a/Telegram/Telegram.vcxproj.filters +++ b/Telegram/Telegram.vcxproj.filters @@ -79,6 +79,9 @@ {ddcc5634-90e7-4815-ba86-a3db539f4774} + + {850c3d13-024a-4ef3-a6b7-b546e67cca48} + @@ -972,9 +975,6 @@ ui - - ui - ui @@ -1068,6 +1068,15 @@ overview + + ui\text + + + ui\text + + + ui\text + @@ -1175,9 +1184,6 @@ ui - - ui - ui\toast @@ -1220,6 +1226,15 @@ overview + + ui\text + + + ui\text + + + ui\text + diff --git a/Telegram/Telegram.xcodeproj/project.pbxproj b/Telegram/Telegram.xcodeproj/project.pbxproj index fe744af85..c994beee8 100644 --- a/Telegram/Telegram.xcodeproj/project.pbxproj +++ b/Telegram/Telegram.xcodeproj/project.pbxproj @@ -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 = ""; }; 120EBCD9A37DB9A36BFE58C0 /* contactsbox.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = contactsbox.h; path = SourceFiles/boxes/contactsbox.h; sourceTree = ""; }; 1292B92B4848460640F6A391 /* telegram.qrc */ = {isa = PBXFileReference; lastKnownFileType = text; name = telegram.qrc; path = Resources/telegram.qrc; sourceTree = ""; }; - 135FD3715BFDC50AD7B00E04 /* text.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = text.cpp; path = SourceFiles/ui/text.cpp; sourceTree = ""; }; + 135FD3715BFDC50AD7B00E04 /* text.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = text.cpp; path = SourceFiles/ui/text/text.cpp; sourceTree = ""; }; 143405635D04698F421A12EA /* aboutbox.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = aboutbox.h; path = SourceFiles/boxes/aboutbox.h; sourceTree = ""; }; 14437BFDCD58FF1742EF1B35 /* photocropbox.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = photocropbox.h; path = SourceFiles/boxes/photocropbox.h; sourceTree = ""; }; 152B8D1BCECEB7B0C77E073C /* introwidget.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = introwidget.h; path = SourceFiles/intro/introwidget.h; sourceTree = ""; }; @@ -570,7 +570,7 @@ 6D50D70712776D7ED3B00E5C /* facade.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = facade.cpp; path = SourceFiles/mtproto/facade.cpp; sourceTree = ""; }; 6E1859D714E4471E053D90C9 /* scrollarea.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = scrollarea.cpp; path = SourceFiles/ui/scrollarea.cpp; sourceTree = ""; }; 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 = ""; }; - 6E8FD0ED1B60D43929944CD2 /* text.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = text.h; path = SourceFiles/ui/text.h; sourceTree = ""; }; + 6E8FD0ED1B60D43929944CD2 /* text.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = text.h; path = SourceFiles/ui/text/text.h; sourceTree = ""; }; 710C982FC773400941B3AFBC /* dropdown.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = dropdown.cpp; path = SourceFiles/dropdown.cpp; sourceTree = ""; }; 723F90793B2C195E2CCB2233 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = /System/Library/Frameworks/AppKit.framework; sourceTree = ""; }; 73737DC91E390C4AB18FB595 /* pspecific_mac_p.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = pspecific_mac_p.mm; path = SourceFiles/pspecific_mac_p.mm; sourceTree = ""; };