From 9ede565a005311f54f77eb1c63d72aa03527d22a Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 7 Apr 2015 01:15:29 +0300 Subject: [PATCH] webPage links preview previews done --- Telegram/Resources/lang.strings | 6 +- Telegram/Resources/style.txt | 6 +- Telegram/SourceFiles/boxes/passcodebox.cpp | 4 +- Telegram/SourceFiles/boxes/sessionsbox.cpp | 59 +- Telegram/SourceFiles/boxes/sessionsbox.h | 4 +- Telegram/SourceFiles/config.h | 14 +- Telegram/SourceFiles/gui/contextmenu.cpp | 3 + Telegram/SourceFiles/gui/flattextarea.cpp | 128 ++- Telegram/SourceFiles/gui/flattextarea.h | 14 +- Telegram/SourceFiles/gui/text.cpp | 793 ++++++++----------- Telegram/SourceFiles/gui/text.h | 113 +++ Telegram/SourceFiles/history.cpp | 34 +- Telegram/SourceFiles/history.h | 1 + Telegram/SourceFiles/historywidget.cpp | 306 +++++-- Telegram/SourceFiles/historywidget.h | 23 +- Telegram/SourceFiles/localstorage.cpp | 7 +- Telegram/SourceFiles/localstorage.h | 3 +- Telegram/SourceFiles/mainwidget.cpp | 12 +- Telegram/SourceFiles/mainwidget.h | 2 +- Telegram/SourceFiles/mtproto/generate.py | 5 +- Telegram/SourceFiles/mtproto/mtpConnection.h | 1 + Telegram/SourceFiles/mtproto/mtpScheme.cpp | 31 +- Telegram/SourceFiles/mtproto/mtpScheme.h | 134 +++- Telegram/SourceFiles/mtproto/scheme.tl | 7 +- Telegram/SourceFiles/structs.h | 13 + 25 files changed, 1093 insertions(+), 630 deletions(-) diff --git a/Telegram/Resources/lang.strings b/Telegram/Resources/lang.strings index 6f305a90cb..9c6e636e27 100644 --- a/Telegram/Resources/lang.strings +++ b/Telegram/Resources/lang.strings @@ -261,6 +261,7 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org "lng_passcode_autolock_minutes" = "{count:_not_used_|# minute|# minutes}"; "lng_passcode_autolock_hours" = "{count:_not_used_|# hour|# hours}"; "lng_passcode_enter_old" = "Enter old passcode"; +"lng_passcode_enter_first" = "Enter a passcode"; "lng_passcode_enter_new" = "Enter new passcode"; "lng_passcode_confirm_new" = "Re-enter new passcode"; "lng_passcode_about" = "When a local passcode is set, a lock icon appears in the top menu. Click it to lock the app.\n\nNote: if you forget your local passcode, you'll need to relogin in Telegram Desktop."; @@ -278,6 +279,7 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org "lng_cloud_password_set" = "Enable two-step verification"; "lng_cloud_password_edit" = "Change cloud password"; "lng_cloud_password_enter_old" = "Enter old password"; +"lng_cloud_password_enter_first" = "Enter a password"; "lng_cloud_password_enter_new" = "Enter new password"; "lng_cloud_password_confirm_new" = "Re-enter new password"; "lng_cloud_password_hint" = "Enter password hint"; @@ -322,11 +324,13 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org "lng_settings_restart_later" = "Later"; "lng_sessions_header" = "Current session"; -"lng_sessions_other_header" = "Other sessions"; +"lng_sessions_other_header" = "Active sessions"; "lng_sessions_no_other" = "No other sessions"; "lng_sessions_other_desc" = "You can log in to Telegram from other\nmobile, tablet and desktop devices, using\nthe same phone number. All your data\nwill be instantly synchronized."; "lng_sessions_terminate_all" = "Terminate all"; +"lng_preview_loading" = "Getting Link Info.."; + "lng_profile_chat_unaccessible" = "Group is unaccessible"; "lng_topbar_info" = "Info"; "lng_profile_settings_section" = "Settings"; diff --git a/Telegram/Resources/style.txt b/Telegram/Resources/style.txt index fbc1ea404e..4f1833d9fe 100644 --- a/Telegram/Resources/style.txt +++ b/Telegram/Resources/style.txt @@ -1773,8 +1773,8 @@ mentionFont: linkFont; mentionPhotoSize: msgPhotoSize; sessionsHeight: 440px; -sessionHeight: 50px; -sessionPadding: margins(20px, 7px, 20px, 0); +sessionHeight: 70px; +sessionPadding: margins(20px, 10px, 20px, 0); sessionsCloseButton: flatButton(aboutCloseButton) { width: boxWidth; } @@ -1783,7 +1783,7 @@ sessionActiveFont: msgDateFont; sessionActiveColor: #aaa; sessionInfoFont: msgFont; sessionInfoColor: dlgTextColor; -sessionTerminateTop: 17px; +sessionTerminateTop: 30px; sessionTerminateSkip: 10px; sessionTerminate: iconedButton(notifyClose) { iconPos: point(3px, 3px); diff --git a/Telegram/SourceFiles/boxes/passcodebox.cpp b/Telegram/SourceFiles/boxes/passcodebox.cpp index def5207964..fba856c107 100644 --- a/Telegram/SourceFiles/boxes/passcodebox.cpp +++ b/Telegram/SourceFiles/boxes/passcodebox.cpp @@ -30,7 +30,7 @@ _about(st::boxWidth - st::addContactPadding.left() - st::addContactPadding.right _saveButton(this, lang(_turningOff ? lng_passcode_remove_button : lng_settings_save), st::btnSelectDone), _cancelButton(this, lang(lng_cancel), st::btnSelectCancel), _oldPasscode(this, st::inpAddContact, lang(lng_passcode_enter_old)), -_newPasscode(this, st::inpAddContact, lang(lng_passcode_enter_new)), +_newPasscode(this, st::inpAddContact, lang(cHasPasscode() ? lng_passcode_enter_new : lng_passcode_enter_first)), _reenterPasscode(this, st::inpAddContact, lang(lng_passcode_confirm_new)), _passwordHint(this, st::inpAddContact, lang(lng_cloud_password_hint)), _recoverEmail(this, st::inpAddContact, lang(lng_cloud_password_email)), @@ -45,7 +45,7 @@ _about(st::boxWidth - st::addContactPadding.left() - st::addContactPadding.right _saveButton(this, lang(_turningOff ? lng_passcode_remove_button : lng_settings_save), st::btnSelectDone), _cancelButton(this, lang(lng_cancel), st::btnSelectCancel), _oldPasscode(this, st::inpAddContact, lang(lng_cloud_password_enter_old)), -_newPasscode(this, st::inpAddContact, lang(lng_cloud_password_enter_new)), +_newPasscode(this, st::inpAddContact, lang(curSalt.isEmpty() ? lng_cloud_password_enter_first : lng_cloud_password_enter_new)), _reenterPasscode(this, st::inpAddContact, lang(lng_cloud_password_confirm_new)), _passwordHint(this, st::inpAddContact, lang(lng_cloud_password_hint)), _recoverEmail(this, st::inpAddContact, lang(lng_cloud_password_email)), diff --git a/Telegram/SourceFiles/boxes/sessionsbox.cpp b/Telegram/SourceFiles/boxes/sessionsbox.cpp index 1fc410fe03..59899ca1c9 100644 --- a/Telegram/SourceFiles/boxes/sessionsbox.cpp +++ b/Telegram/SourceFiles/boxes/sessionsbox.cpp @@ -36,7 +36,7 @@ void SessionsInner::paintEvent(QPaintEvent *e) { p.fillRect(r, st::white->b); p.setFont(st::linkFont->f); - int32 x = st::sessionPadding.left(), xact = st::sessionTerminateSkip + st::sessionTerminate.width + st::sessionTerminateSkip; + int32 x = st::sessionPadding.left(), xact = st::sessionTerminateSkip + st::sessionTerminate.iconPos.x();// st::sessionTerminateSkip + st::sessionTerminate.width + st::sessionTerminateSkip; int32 w = width() - 2 * x, availw = width() - 2 * xact; int32 from = (r.top() >= 0) ? qFloor(r.top() / st::sessionHeight) : 0, count = _list->size(); if (from < count) { @@ -55,8 +55,10 @@ void SessionsInner::paintEvent(QPaintEvent *e) { p.drawTextRight(xact, st::sessionPadding.top(), availw, auth.active, auth.activeWidth); p.setFont(st::sessionInfoFont->f); - p.setPen(st::sessionInfoColor->p); + p.setPen(st::black->p); p.drawTextLeft(x, st::sessionPadding.top() + st::sessionNameFont->height, w, auth.info, auth.infoWidth); + p.setPen(st::sessionInfoColor->p); + p.drawTextLeft(x, st::sessionPadding.top() + st::sessionNameFont->height + st::sessionInfoFont->height, w, auth.ip, auth.ipWidth); p.translate(0, st::sessionHeight); } @@ -221,9 +223,10 @@ void SessionsBox::paintEvent(QPaintEvent *e) { p.drawTextRight(x, st::sessionPadding.top(), w, _current.active, _current.activeWidth); p.setFont(st::sessionInfoFont->f); - p.setPen(st::sessionInfoColor->p); + p.setPen(st::black->p); p.drawTextLeft(x, st::sessionPadding.top() + st::sessionNameFont->height, w, _current.info, _current.infoWidth); - + p.setPen(st::sessionInfoColor->p); + p.drawTextLeft(x, st::sessionPadding.top() + st::sessionNameFont->height + st::sessionInfoFont->height, w, _current.ip, _current.ipWidth); p.translate(0, st::sessionHeight); if (_list.isEmpty()) { paintTitle(p, lang(lng_sessions_no_other), true); @@ -245,7 +248,7 @@ void SessionsBox::gotAuthorizations(const MTPaccount_Authorizations &result) { _shortPollRequest = 0; int32 availCurrent = st::boxWidth - st::sessionPadding.left() - st::sessionTerminateSkip; - int32 availOther = availCurrent - st::sessionTerminate.width - st::sessionTerminateSkip; + int32 availOther = availCurrent - st::sessionTerminate.iconPos.x();// -st::sessionTerminate.width - st::sessionTerminateSkip; _list.clear(); const QVector &v(result.c_account_authorizations().vauthorizations.c_vector().v); @@ -259,29 +262,39 @@ void SessionsBox::gotAuthorizations(const MTPaccount_Authorizations &result) { SessionData data; data.hash = d.vhash.v; - QString appName, systemVer = qs(d.vsystem_version), deviceModel = qs(d.vdevice_model); - if (d.vapi_id.v == 17349) { - appName = qs(d.vapp_name);// (d.vapi_id.v == 2040) ? qsl("Telegram Desktop") : qsl("Telegram Desktop (GitHub)"); - if (systemVer == QLatin1String("windows")) { - deviceModel = qsl("Windows"); - } else if (systemVer == QLatin1String("os x")) { - deviceModel = qsl("Mac OS X"); - } else if (systemVer == QLatin1String("linux")) { - deviceModel = qsl("Linux"); + QString appName, appVer = qs(d.vapp_version), systemVer = qs(d.vsystem_version), deviceModel = qs(d.vdevice_model); + if (d.vapi_id.v == 2040 || d.vapi_id.v == 17349) { + appName = (d.vapi_id.v == 2040) ? qsl("Telegram Desktop") : qsl("Telegram Desktop (GitHub)"); + // if (systemVer == QLatin1String("windows")) { + // deviceModel = qsl("Windows"); + // } else if (systemVer == QLatin1String("os x")) { + // deviceModel = qsl("OS X"); + // } else if (systemVer == QLatin1String("linux")) { + // deviceModel = qsl("Linux"); + // } + if (appVer == QString::number(appVer.toInt())) { + int32 ver = appVer.toInt(); + appVer = QString("%1.%2").arg(ver / 1000000).arg((ver % 1000000) / 1000) + ((ver % 1000) ? ('.' + QString::number(ver % 1000)) : QString()); + } else { + appVer = QString(); } } else { appName = qs(d.vapp_name);// +qsl(" for ") + qs(d.vplatform); + if (appVer.indexOf('(') >= 0) appVer = appVer.mid(appVer.indexOf('(')); } data.name = appName; + if (!appVer.isEmpty()) data.name += ' ' + appVer; data.nameWidth = st::sessionNameFont->m.width(data.name); - QString country = qs(d.vcountry); - CountriesByISO2::const_iterator j = countries.constFind(country); - if (j != countries.cend()) country = QString::fromUtf8(j.value()->name); + QString country = qs(d.vcountry), platform = qs(d.vplatform); + //CountriesByISO2::const_iterator j = countries.constFind(country); + //if (j != countries.cend()) country = QString::fromUtf8(j.value()->name); MTPint active = d.vdate_active.v ? d.vdate_active : d.vdate_created; data.activeTime = active.v; - data.info = country + QLatin1String(" (") + qs(d.vip) + QLatin1String("), ") + deviceModel; + + data.info = qs(d.vdevice_model) + QLatin1String(", ") + (platform.isEmpty() ? QString() : platform + ' ') + qs(d.vsystem_version); + data.ip = qs(d.vip) + (country.isEmpty() ? QString() : QString::fromUtf8(" \xe2\x80\x93 ") + country); if (!data.hash || (d.vflags.v & 1)) { data.active = QString(); data.activeWidth = 0; @@ -294,6 +307,11 @@ void SessionsBox::gotAuthorizations(const MTPaccount_Authorizations &result) { data.info = st::sessionInfoFont->m.elidedText(data.info, Qt::ElideRight, availCurrent); data.infoWidth = st::sessionInfoFont->m.width(data.info); } + data.ipWidth = st::sessionInfoFont->m.width(data.ip); + if (data.ipWidth > availCurrent) { + data.ip = st::sessionInfoFont->m.elidedText(data.ip, Qt::ElideRight, availCurrent); + data.ipWidth = st::sessionInfoFont->m.width(data.ip); + } _current = data; } else { QDateTime now(QDateTime::currentDateTime()), lastTime(date(active)); @@ -317,6 +335,11 @@ void SessionsBox::gotAuthorizations(const MTPaccount_Authorizations &result) { data.info = st::sessionInfoFont->m.elidedText(data.info, Qt::ElideRight, availOther); data.infoWidth = st::sessionInfoFont->m.width(data.info); } + data.ipWidth = st::sessionInfoFont->m.width(data.ip); + if (data.ipWidth > availOther) { + data.ip = st::sessionInfoFont->m.elidedText(data.ip, Qt::ElideRight, availOther); + data.ipWidth = st::sessionInfoFont->m.width(data.ip); + } _list.push_back(data); for (int32 i = _list.size(); i > 1;) { diff --git a/Telegram/SourceFiles/boxes/sessionsbox.h b/Telegram/SourceFiles/boxes/sessionsbox.h index 2ddd97943b..d2859bbfae 100644 --- a/Telegram/SourceFiles/boxes/sessionsbox.h +++ b/Telegram/SourceFiles/boxes/sessionsbox.h @@ -25,8 +25,8 @@ struct SessionData { uint64 hash; int32 activeTime; - int32 nameWidth, activeWidth, infoWidth; - QString name, active, info; + int32 nameWidth, activeWidth, infoWidth, ipWidth; + QString name, active, info, ip; }; typedef QList SessionsList; diff --git a/Telegram/SourceFiles/config.h b/Telegram/SourceFiles/config.h index 6f93faa061..81bfa0668a 100644 --- a/Telegram/SourceFiles/config.h +++ b/Telegram/SourceFiles/config.h @@ -218,18 +218,20 @@ static const char *ApiHash = "344583e45741c457fe1862106095a5eb"; inline const char *cApiDeviceModel() { #ifdef Q_OS_WIN - return "x86 desktop"; -#else - return "x64 desktop"; + return "PC"; +#elif defined Q_OS_MAC + return "Mac"; +#elif defined Q_OS_LINUX + return "PC"; #endif } inline const char *cApiSystemVersion() { #ifdef Q_OS_WIN - return "windows"; + return "Windows"; #elif defined Q_OS_MAC - return "os x"; + return "OS X"; #elif defined Q_OS_LINUX - return "linux"; + return "Linux"; #endif } inline QString cApiAppVersion() { diff --git a/Telegram/SourceFiles/gui/contextmenu.cpp b/Telegram/SourceFiles/gui/contextmenu.cpp index b70d47197e..ae0b1253cf 100644 --- a/Telegram/SourceFiles/gui/contextmenu.cpp +++ b/Telegram/SourceFiles/gui/contextmenu.cpp @@ -152,6 +152,9 @@ void ContextMenu::keyPressEvent(QKeyEvent *e) { emit _buttons[_selected]->clicked(); return; } + } else if (e->key() == Qt::Key_Escape) { + hideStart(); + return; } if ((e->key() != Qt::Key_Up && e->key() != Qt::Key_Down) || _buttons.size() < 1) return; diff --git a/Telegram/SourceFiles/gui/flattextarea.cpp b/Telegram/SourceFiles/gui/flattextarea.cpp index cebf39879f..e7f4c6fbcb 100644 --- a/Telegram/SourceFiles/gui/flattextarea.cpp +++ b/Telegram/SourceFiles/gui/flattextarea.cpp @@ -68,10 +68,6 @@ void FlatTextarea::onTouchTimer() { _touchRightButton = true; } -void FlatTextarea::insertFromMimeData(const QMimeData *source) { - QTextEdit::insertFromMimeData(source); -} - bool FlatTextarea::viewportEvent(QEvent *e) { if (e->type() == QEvent::TouchBegin || e->type() == QEvent::TouchUpdate || e->type() == QEvent::TouchEnd || e->type() == QEvent::TouchCancel) { QTouchEvent *ev = static_cast(e); @@ -420,6 +416,106 @@ bool FlatTextarea::isRedoAvailable() const { return _redoAvailable; } +void FlatTextarea::parseLinks() { // some code is duplicated in text.cpp! + LinkRanges newLinks; + + QString text(toPlainText()); + if (text.isEmpty()) { + if (!_links.isEmpty()) { + _links.clear(); + emit linksChanged(); + } + return; + } + + initLinkSets(); + + int32 len = text.size(); + const QChar *start = text.unicode(), *end = start + text.size(); + for (int32 offset = 0, matchOffset = offset; offset < len;) { + QRegularExpressionMatch m = reDomain().match(text, matchOffset); + if (!m.hasMatch()) break; + + int32 domainOffset = m.capturedStart(); + + QString protocol = m.captured(1).toLower(); + QString topDomain = m.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() && domainOffset > offset + 1 && *(start + domainOffset - 1) == QChar('@')) { + QString forMailName = text.mid(offset, domainOffset - offset - 1); + QRegularExpressionMatch mMailName = reMailName().match(forMailName); + if (mMailName.hasMatch()) { + offset = matchOffset = m.capturedEnd(); + continue; + } + } + if (!isProtocolValid || !isTopDomainValid) { + offset = matchOffset = m.capturedEnd(); + continue; + } + + QStack parenth; + const QChar *domainEnd = start + m.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; + } + } + newLinks.push_back(qMakePair(domainOffset - 1, p - start - domainOffset + 2)); + offset = matchOffset = p - start; + } + + if (newLinks != _links) { + _links = newLinks; + emit linksChanged(); + } +} + +QStringList FlatTextarea::linksList() const { + QStringList result; + if (!_links.isEmpty()) { + QString text(toPlainText()); + for (LinkRanges::const_iterator i = _links.cbegin(), e = _links.cend(); i != e; ++i) { + result.push_back(text.mid(i->first + 1, i->second - 2)); + } + } + return result; +} + +void FlatTextarea::insertFromMimeData(const QMimeData *source) { + QTextEdit::insertFromMimeData(source); + emit spacedReturnedPasted(); +} + void FlatTextarea::insertEmoji(EmojiPtr emoji, QTextCursor c) { c.removeSelectedText(); @@ -512,6 +608,22 @@ void FlatTextarea::processDocumentContentsChange(int position, int charsAdded) { } void FlatTextarea::onDocumentContentsChange(int position, int charsRemoved, int charsAdded) { + if (!_links.isEmpty()) { + bool changed = false; + for (LinkRanges::iterator i = _links.begin(); i != _links.end();) { + if (i->first + i->second <= position) { + ++i; + } else if (i->first >= position + charsRemoved) { + i->first += charsAdded - charsRemoved; + ++i; + } else { + i = _links.erase(i); + changed = true; + } + } + if (changed) emit linksChanged(); + } + if (_replacingEmojis || document()->availableRedoSteps() > 0) return; const int takeBack = 3; @@ -626,6 +738,13 @@ void FlatTextarea::keyPressEvent(QKeyEvent *e) { if (enter && ctrl) { e->setModifiers(e->modifiers() & ~Qt::ControlModifier); } + bool spaceOrReturn = false; + QString t(e->text()); + if (!t.isEmpty() && t.size() < 3) { + if (t.at(0) == '\n' || t.at(0) == '\r' || t.at(0).isSpace() || t.at(0) == QChar::LineSeparator) { + spaceOrReturn = true; + } + } QTextEdit::keyPressEvent(e); if (tc == textCursor()) { bool check = false; @@ -644,6 +763,7 @@ void FlatTextarea::keyPressEvent(QKeyEvent *e) { } } } + if (spaceOrReturn) emit spacedReturnedPasted(); } } diff --git a/Telegram/SourceFiles/gui/flattextarea.h b/Telegram/SourceFiles/gui/flattextarea.h index f221204b45..d684f017c0 100644 --- a/Telegram/SourceFiles/gui/flattextarea.h +++ b/Telegram/SourceFiles/gui/flattextarea.h @@ -27,9 +27,6 @@ class FlatTextarea : public QTextEdit, public Animated { public: FlatTextarea(QWidget *parent, const style::flatTextarea &st, const QString &ph = QString(), const QString &val = QString()); - QString val() const; - - void insertFromMimeData(const QMimeData *source); bool viewportEvent(QEvent *e); void touchEvent(QTouchEvent *e); @@ -59,6 +56,11 @@ public: bool isUndoAvailable() const; bool isRedoAvailable() const; + void parseLinks(); + QStringList linksList() const; + + void insertFromMimeData(const QMimeData *source); + public slots: void onTouchTimer(); @@ -77,6 +79,8 @@ signals: void submitted(bool ctrlShiftEnter); void cancelled(); void tabbed(); + void spacedReturnedPasted(); + void linksChanged(); protected: @@ -108,4 +112,8 @@ private: typedef QPair Insertion; typedef QList Insertions; Insertions _insertions; + + typedef QPair LinkRange; + typedef QList LinkRanges; + LinkRanges _links; }; diff --git a/Telegram/SourceFiles/gui/text.cpp b/Telegram/SourceFiles/gui/text.cpp index 2ad36c315c..66aec9b1b4 100644 --- a/Telegram/SourceFiles/gui/text.cpp +++ b/Telegram/SourceFiles/gui/text.cpp @@ -24,122 +24,13 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org namespace { - inline bool chIsSpace(QChar ch, bool rich = false) { - return ch.isSpace() || (ch < 32 && !(rich && ch == TextCommand)) || (ch == QChar::ParagraphSeparator) || (ch == QChar::LineSeparator) || (ch == QChar::ObjectReplacementCharacter) || (ch == QChar::SoftHyphen) || (ch == QChar::CarriageReturn) || (ch == QChar::Tabulation); - } - inline bool chIsBad(QChar ch) { - return (ch == 0) || (ch >= 8232 && ch < 8239) || (ch >= 65024 && ch < 65040 && ch != 65039) || (ch >= 127 && ch < 160 && ch != 156); - } - inline bool chIsTrimmed(QChar ch, bool rich = false) { - return (!rich || ch != TextCommand) && (chIsSpace(ch) || chIsBad(ch)); - } - inline bool chIsDiac(QChar ch) { // diac and variation selectors - return (ch >= 768 && ch < 880) || (ch >= 7616 && ch < 7680) || (ch >= 8400 && ch < 8448) || (ch >= 65056 && ch < 65072); - } - inline int32 chMaxDiacAfterSymbol() { - return 4; - } - inline bool chIsNewline(QChar ch) { - return (ch == QChar::LineFeed || ch == 156); - } - inline bool chIsLinkEnd(QChar ch) { - return ch == TextCommand || chIsBad(ch) || chIsSpace(ch) || chIsNewline(ch) || ch.isLowSurrogate() || ch.isHighSurrogate(); - } - inline bool chIsAlmostLinkEnd(QChar ch) { - switch (ch.unicode()) { - case '?': - case ',': - case '.': - case '"': - case ':': - case '!': - case '\'': - return true; - default: - break; - } - return false; - } - inline bool chIsWordSeparator(QChar ch) { - switch (ch.unicode()) { - case QChar::Space: - case QChar::LineFeed: - case '.': - case ',': - case '?': - case '!': - case '@': - case '#': - case '$': - case ':': - case ';': - case '-': - case '<': - case '>': - case '[': - case ']': - case '(': - case ')': - case '{': - case '}': - case '=': - case '/': - case '+': - case '%': - case '&': - case '^': - case '*': - case '\'': - case '"': - case '`': - case '~': - case '|': - return true; - default: - break; - } - return false; - } - inline bool chIsSentenceEnd(QChar ch) { - switch (ch.unicode()) { - case '.': - case '?': - case '!': - return true; - default: - break; - } - return false; - } - inline bool chIsSentencePartEnd(QChar ch) { - switch (ch.unicode()) { - case ',': - case ':': - case ';': - return true; - default: - break; - } - return false; - } - inline bool chIsParagraphSeparator(QChar ch) { - switch (ch.unicode()) { - case QChar::LineFeed: - return true; - default: - break; - } - return false; - } - const QRegularExpression _reDomain(QString::fromUtf8("(?|'\"\\[\\]\\{\\}`\\~\\!\\%\\^\\*\\(\\)\\-\\+=\\x10])#[\\w]{2,64}([\\W]|$)"), QRegularExpression::UseUnicodePropertiesOption); const QRegularExpression _reMention(qsl("(^|[\\s\\.,:;<>|'\"\\[\\]\\{\\}`\\~\\!\\%\\^\\*\\(\\)\\-\\+=\\x10])@[A-Za-z_0-9]{5,32}([\\W]|$)"), QRegularExpression::UseUnicodePropertiesOption); - QSet validProtocols, validTopDomains; - void initLinkSets(); + QSet _validProtocols, _validTopDomains; const style::textStyle *_textStyle = 0; @@ -158,6 +49,14 @@ namespace { } } +const QRegularExpression &reDomain() { + return _reDomain; +} + +const QRegularExpression &reMailName() { + return _reMailName; +} + const QRegularExpression &reHashtag() { return _reHashtag; } @@ -3004,337 +2903,349 @@ SkipBlock::SkipBlock(const style::font &font, const QString &str, uint16 from, i namespace { void regOneProtocol(const QString &protocol) { - validProtocols.insert(hashCrc32(protocol.constData(), protocol.size() * sizeof(QChar))); + _validProtocols.insert(hashCrc32(protocol.constData(), protocol.size() * sizeof(QChar))); } void regOneTopDomain(const QString &domain) { - validTopDomains.insert(hashCrc32(domain.constData(), domain.size() * sizeof(QChar))); + _validTopDomains.insert(hashCrc32(domain.constData(), domain.size() * sizeof(QChar))); } - void initLinkSets() { - 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("рф")); - } +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) { @@ -4159,12 +4070,10 @@ bool textSplit(QString &sendingText, QString &leftText, int32 limit) { return true; } -LinkRanges textParseLinks(const QString &text, bool rich) { +LinkRanges textParseLinks(const QString &text, bool rich) { // some code is duplicated in flattextarea.cpp! LinkRanges lnkRanges; - if (validProtocols.empty()) { - initLinkSets(); - } + initLinkSets(); int32 len = text.size(), nextCmd = rich ? 0 : len; const QChar *start = text.unicode(), *end = start + text.size(); for (int32 offset = 0, matchOffset = offset; offset < len;) { @@ -4249,8 +4158,8 @@ LinkRanges textParseLinks(const QString &text, bool rich) { 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))); + 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() && domainOffset > offset + 1 && *(start + domainOffset - 1) == QChar('@')) { QString forMailName = text.mid(offset, domainOffset - offset - 1); diff --git a/Telegram/SourceFiles/gui/text.h b/Telegram/SourceFiles/gui/text.h index f2683066e5..e9b42e3d56 100644 --- a/Telegram/SourceFiles/gui/text.h +++ b/Telegram/SourceFiles/gui/text.h @@ -492,6 +492,11 @@ private: }; +void initLinkSets(); +const QSet &validProtocols(); +const QSet &validTopDomains(); +const QRegularExpression &reDomain(); +const QRegularExpression &reMailName(); const QRegularExpression &reHashtag(); // text style @@ -521,3 +526,111 @@ QString textcmdStopColor(); const QChar *textSkipCommand(const QChar *from, const QChar *end, bool canLink = true); QString textEmojiString(EmojiPtr emoji); + +inline bool chIsSpace(QChar ch, bool rich = false) { + return ch.isSpace() || (ch < 32 && !(rich && ch == TextCommand)) || (ch == QChar::ParagraphSeparator) || (ch == QChar::LineSeparator) || (ch == QChar::ObjectReplacementCharacter) || (ch == QChar::SoftHyphen) || (ch == QChar::CarriageReturn) || (ch == QChar::Tabulation); +} +inline bool chIsBad(QChar ch) { + return (ch == 0) || (ch >= 8232 && ch < 8239) || (ch >= 65024 && ch < 65040 && ch != 65039) || (ch >= 127 && ch < 160 && ch != 156); +} +inline bool chIsTrimmed(QChar ch, bool rich = false) { + return (!rich || ch != TextCommand) && (chIsSpace(ch) || chIsBad(ch)); +} +inline bool chIsDiac(QChar ch) { // diac and variation selectors + return (ch >= 768 && ch < 880) || (ch >= 7616 && ch < 7680) || (ch >= 8400 && ch < 8448) || (ch >= 65056 && ch < 65072); +} +inline int32 chMaxDiacAfterSymbol() { + return 4; +} +inline bool chIsNewline(QChar ch) { + return (ch == QChar::LineFeed || ch == 156); +} +inline bool chIsLinkEnd(QChar ch) { + return ch == TextCommand || chIsBad(ch) || chIsSpace(ch) || chIsNewline(ch) || ch.isLowSurrogate() || ch.isHighSurrogate(); +} +inline bool chIsAlmostLinkEnd(QChar ch) { + switch (ch.unicode()) { + case '?': + case ',': + case '.': + case '"': + case ':': + case '!': + case '\'': + return true; + default: + break; + } + return false; +} +inline bool chIsWordSeparator(QChar ch) { + switch (ch.unicode()) { + case QChar::Space: + case QChar::LineFeed: + case '.': + case ',': + case '?': + case '!': + case '@': + case '#': + case '$': + case ':': + case ';': + case '-': + case '<': + case '>': + case '[': + case ']': + case '(': + case ')': + case '{': + case '}': + case '=': + case '/': + case '+': + case '%': + case '&': + case '^': + case '*': + case '\'': + case '"': + case '`': + case '~': + case '|': + return true; + default: + break; + } + return false; +} +inline bool chIsSentenceEnd(QChar ch) { + switch (ch.unicode()) { + case '.': + case '?': + case '!': + return true; + default: + break; + } + return false; +} +inline bool chIsSentencePartEnd(QChar ch) { + switch (ch.unicode()) { + case ',': + case ':': + case ';': + return true; + default: + break; + } + return false; +} +inline bool chIsParagraphSeparator(QChar ch) { + switch (ch.unicode()) { + case QChar::LineFeed: + return true; + default: + break; + } + return false; +} diff --git a/Telegram/SourceFiles/history.cpp b/Telegram/SourceFiles/history.cpp index d6b982c8f1..ca81865d67 100644 --- a/Telegram/SourceFiles/history.cpp +++ b/Telegram/SourceFiles/history.cpp @@ -62,7 +62,7 @@ namespace { Qt::LayoutDirectionAuto, // dir }; TextParseOptions _webpageDescriptionOptions = { - TextParseMultiline | TextParseRichText, // flags + /*TextParseLinks | */TextParseMultiline | TextParseRichText, // flags 0, // maxw 0, // maxh Qt::LayoutDirectionAuto, // dir @@ -71,7 +71,9 @@ namespace { inline void _initTextOptions() { _historySrvOptions.dir = _textNameOptions.dir = _textDlgOptions.dir = langDir(); _textDlgOptions.maxw = st::dlgMaxWidth * 2; + _webpageTitleOptions.maxw = st::msgMaxWidth - st::msgPadding.left() - st::msgPadding.right() - st::webPageLeft; _webpageTitleOptions.maxh = st::webPageTitleFont->height * 2; + _webpageDescriptionOptions.maxw = st::msgMaxWidth - st::msgPadding.left() - st::msgPadding.right() - st::webPageLeft; _webpageDescriptionOptions.maxh = st::webPageDescriptionFont->height * 3; } @@ -1869,17 +1871,7 @@ void HistoryPhoto::draw(QPainter &p, const HistoryItem *parent, bool selected, i } ImagePtr HistoryPhoto::replyPreview() { - if (data->replyPreview->isNull() && !data->thumb->isNull()) { - if (data->thumb->loaded()) { - int w = data->thumb->width(), h = data->thumb->height(); - if (w <= 0) w = 1; - if (h <= 0) h = 1; - data->replyPreview = ImagePtr(w > h ? data->thumb->pix(w * st::msgReplyBarSize.height() / h, st::msgReplyBarSize.height()) : data->thumb->pix(st::msgReplyBarSize.height()), "PNG"); - } else { - data->thumb->load(); - } - } - return data->replyPreview; + return data->makeReplyPreview(); } QString formatSizeText(qint64 size) { @@ -3146,7 +3138,7 @@ void HistoryContact::updateFrom(const MTPMessageMedia &media) { HistoryWebPage::HistoryWebPage(WebPageData *data) : HistoryMedia() , data(data) , _openl(data->url.isEmpty() ? 0 : new TextLink(data->url)) -, _photol(data->photo ? new PhotoLink(data->photo) : 0) +, _photol((data->photo && data->type != WebPageVideo) ? new PhotoLink(data->photo) : 0) , _asArticle(false) , _title(st::msgMinWidth - st::webPageLeft) , _description(st::msgMinWidth - st::webPageLeft) @@ -3163,6 +3155,7 @@ void HistoryWebPage::initDimensions(const HistoryItem *parent) { return; } if (!_openl && !data->url.isEmpty()) _openl = TextLinkPtr(new TextLink(data->url)); + if (!_photol && data->photo && data->type != WebPageVideo) _photol = TextLinkPtr(new PhotoLink(data->photo)); if (data->photo && data->type != WebPagePhoto && data->type != WebPageVideo) { if (data->type == WebPageProfile) { _asArticle = true; @@ -3224,7 +3217,7 @@ void HistoryWebPage::initDimensions(const HistoryItem *parent) { _minh += st::webPageTitleFont->height; } } - if (!data->description.isEmpty() && data->siteName != QLatin1String("YouTube")) { + if (!data->description.isEmpty()) { _description.setText(st::webPageDescriptionFont, textClean(data->description), _webpageDescriptionOptions); if (_asArticle) { _maxw = qMax(_maxw, int32(st::webPageLeft + _description.maxWidth() + st::webPagePhotoDelta + st::webPagePhotoSize)); @@ -3546,18 +3539,7 @@ HistoryMedia *HistoryWebPage::clone() const { } ImagePtr HistoryWebPage::replyPreview() { - if (!data->photo) return ImagePtr(); - if (data->photo->replyPreview->isNull() && !data->photo->thumb->isNull()) { - if (data->photo->thumb->loaded()) { - int w = data->photo->thumb->width(), h = data->photo->thumb->height(); - if (w <= 0) w = 1; - if (h <= 0) h = 1; - data->photo->replyPreview = ImagePtr(w > h ? data->photo->thumb->pix(w * st::msgReplyBarSize.height() / h, st::msgReplyBarSize.height()) : data->photo->thumb->pix(st::msgReplyBarSize.height()), "PNG"); - } else { - data->photo->thumb->load(); - } - } - return data->photo->replyPreview; + return data->photo ? data->photo->makeReplyPreview() : ImagePtr(); } namespace { diff --git a/Telegram/SourceFiles/history.h b/Telegram/SourceFiles/history.h index 6448b7d7c2..3a90b5a1ec 100644 --- a/Telegram/SourceFiles/history.h +++ b/Telegram/SourceFiles/history.h @@ -245,6 +245,7 @@ struct History : public QList { QString draft; MsgId draftToId; MessageCursor draftCursor; + bool draftPreviewCancelled; int32 lastWidth, lastScrollTop; bool mute; diff --git a/Telegram/SourceFiles/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp index 7e0a35f91d..766c20935b 100644 --- a/Telegram/SourceFiles/historywidget.cpp +++ b/Telegram/SourceFiles/historywidget.cpp @@ -1296,7 +1296,7 @@ void MessageField::insertFromMimeData(const QMimeData *source) { return; } } - return FlatTextarea::insertFromMimeData(source); + FlatTextarea::insertFromMimeData(source); } void MessageField::focusInEvent(QFocusEvent *e) { @@ -1563,7 +1563,10 @@ HistoryWidget::HistoryWidget(QWidget *parent) : QWidget(parent) , _replyToId(0) , _replyTo(0) , _replyToNameVersion(0) -, _replyForwardCancel(this, st::replyCancel) +, _replyForwardPreviewCancel(this, st::replyCancel) +, _previewData(0) +, _previewRequest(0) +, _previewCancelled(false) , _replyReturn(0) , _lastStickersUpdate(0) , _stickersUpdateRequest(0) @@ -1608,7 +1611,7 @@ HistoryWidget::HistoryWidget(QWidget *parent) : QWidget(parent) connect(&_scroll, SIGNAL(scrolled()), this, SLOT(onListScroll())); connect(&_toHistoryEnd, SIGNAL(clicked()), this, SLOT(onHistoryToEnd())); - connect(&_replyForwardCancel, SIGNAL(clicked()), this, SLOT(onReplyForwardCancel())); + connect(&_replyForwardPreviewCancel, SIGNAL(clicked()), this, SLOT(onReplyForwardPreviewCancel())); connect(&_send, SIGNAL(clicked()), this, SLOT(onSend())); connect(&_attachDocument, SIGNAL(clicked()), this, SLOT(onDocumentSelect())); connect(&_attachPhoto, SIGNAL(clicked()), this, SLOT(onPhotoSelect())); @@ -1619,6 +1622,8 @@ HistoryWidget::HistoryWidget(QWidget *parent) : QWidget(parent) connect(&imageLoader, SIGNAL(imageReady()), this, SLOT(onPhotoReady())); connect(&imageLoader, SIGNAL(imageFailed(quint64)), this, SLOT(onPhotoFailed(quint64))); connect(&_field, SIGNAL(changed()), this, SLOT(onTextChange())); + connect(&_field, SIGNAL(spacedReturnedPasted()), this, SLOT(onPreviewParse())); + connect(&_field, SIGNAL(linksChanged()), this, SLOT(onPreviewCheck())); connect(App::wnd()->windowHandle(), SIGNAL(visibleChanged(bool)), this, SLOT(onVisibleChanged())); connect(&_scrollTimer, SIGNAL(timeout()), this, SLOT(onScrollTimer())); connect(&_emojiPan, SIGNAL(emojiSelected(EmojiPtr)), &_field, SLOT(onEmojiInsert(EmojiPtr))); @@ -1626,6 +1631,7 @@ HistoryWidget::HistoryWidget(QWidget *parent) : QWidget(parent) connect(&_emojiPan, SIGNAL(updateStickers()), this, SLOT(updateStickers())); // connect(&_stickerPan, SIGNAL(stickerSelected(DocumentData*)), this, SLOT(onStickerSend(DocumentData*))); connect(&_typingStopTimer, SIGNAL(timeout()), this, SLOT(cancelTyping())); + connect(&_previewTimer, SIGNAL(timeout()), this, SLOT(onPreviewTimeout())); _scrollTimer.setSingleShot(false); @@ -1639,7 +1645,7 @@ HistoryWidget::HistoryWidget(QWidget *parent) : QWidget(parent) connect(_field.verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(onDraftSaveDelayed())); connect(&_field, SIGNAL(cursorPositionChanged()), this, SLOT(onFieldCursorChanged())); - _replyForwardCancel.hide(); + _replyForwardPreviewCancel.hide(); _scroll.hide(); _scroll.move(0, 0); @@ -1691,6 +1697,8 @@ void HistoryWidget::onTextChange() { if (!hist || _synthedTextUpdate) return; _saveDraftText = true; onDraftSave(true); + + if (!_field.hasText()) _previewCancelled = false; } void HistoryWidget::onDraftSaveDelayed() { @@ -1717,12 +1725,12 @@ void HistoryWidget::onDraftSave(bool delayed) { writeDraft(); } -void HistoryWidget::writeDraft(MsgId *replyTo, const QString *text, const MessageCursor *cursor) { +void HistoryWidget::writeDraft(MsgId *replyTo, const QString *text, const MessageCursor *cursor, bool *previewCancelled) { bool save = hist && (_saveDraftStart > 0); _saveDraftStart = 0; _saveDraftTimer.stop(); if (_saveDraftText) { - if (save) Local::writeDraft(hist->peer->id, Local::MessageDraft(replyTo ? (*replyTo) : _replyToId, text ? (*text) : _field.getText())); + if (save) Local::writeDraft(hist->peer->id, Local::MessageDraft(replyTo ? (*replyTo) : _replyToId, text ? (*text) : _field.getText(), previewCancelled ? (*previewCancelled) : _previewCancelled)); _saveDraftText = false; } if (save) Local::writeDraftPositions(hist->peer->id, cursor ? (*cursor) : MessageCursor(_field)); @@ -2009,8 +2017,9 @@ void HistoryWidget::showPeer(const PeerId &peer, MsgId msgId, bool force, bool l hist->draft = _field.getText(); hist->draftCursor.fillFrom(_field); hist->draftToId = _replyToId; + hist->draftPreviewCancelled = _previewCancelled; - writeDraft(&hist->draftToId, &hist->draft, &hist->draftCursor); + writeDraft(&hist->draftToId, &hist->draft, &hist->draftCursor, &hist->draftPreviewCancelled); if (hist->readyForWork() && _scroll.scrollTop() + 1 <= _scroll.scrollTopMax()) { hist->lastWidth = _list->width(); @@ -2024,8 +2033,13 @@ void HistoryWidget::showPeer(const PeerId &peer, MsgId msgId, bool force, bool l if (_replyToId) { _replyTo = 0; _replyToId = 0; - _replyForwardCancel.hide(); + _replyForwardPreviewCancel.hide(); } + if (_previewData && _previewData->pendingTill >= 0) { + _previewData = 0; + if (!App::main()->hasForwardingItems()) _replyForwardPreviewCancel.hide(); + } + _previewCache.clear(); _scroll.setWidget(0); if (_list) _list->deleteLater(); _list = 0; @@ -2099,6 +2113,9 @@ void HistoryWidget::showPeer(const PeerId &peer, MsgId msgId, bool force, bool l _field.setFocus(); hist->draftCursor.applyTo(_field, &_synthedTextUpdate); _replyToId = App::main()->hasForwardingItems() ? 0 : hist->draftToId; + if (hist->draftPreviewCancelled) { + _previewCancelled = true; + } } else { Local::MessageDraft draft = Local::readDraft(hist->peer->id); setFieldText(draft.text); @@ -2108,12 +2125,18 @@ void HistoryWidget::showPeer(const PeerId &peer, MsgId msgId, bool force, bool l cur.applyTo(_field, &_synthedTextUpdate); } _replyToId = App::main()->hasForwardingItems() ? 0 : draft.replyTo; + if (draft.previewCancelled) { + _previewCancelled = true; + } } if (_replyToId) { updateReplyTo(); if (!_replyTo) App::api()->requestReplyTo(0, _replyToId); resizeEvent(0); } + if (!_previewCancelled) { + onPreviewParse(); + } connect(&_scroll, SIGNAL(geometryChanged()), _list, SLOT(onParentGeometryChanged())); connect(&_scroll, SIGNAL(scrolled()), _list, SLOT(onUpdateSelected())); @@ -2156,7 +2179,7 @@ void HistoryWidget::updateControlsVisibility() { _toHistoryEnd.hide(); _attachMention.hide(); _field.hide(); - _replyForwardCancel.hide(); + _replyForwardPreviewCancel.hide(); _attachDocument.hide(); _attachPhoto.hide(); _attachEmoji.hide(); @@ -2183,8 +2206,8 @@ void HistoryWidget::updateControlsVisibility() { if (_field.isHidden()) { _field.show(); } - if ((_replyToId || App::main()->hasForwardingItems()) && _replyForwardCancel.isHidden()) { - _replyForwardCancel.show(); + if ((_replyToId || App::main()->hasForwardingItems() || (_previewData && _previewData->pendingTill >= 0)) && _replyForwardPreviewCancel.isHidden()) { + _replyForwardPreviewCancel.show(); resizeEvent(0); update(); } @@ -2220,7 +2243,7 @@ void HistoryWidget::updateControlsVisibility() { _emojiPan.hide(); // _stickerPan.hide(); _toHistoryEnd.hide(); - _replyForwardCancel.hide(); + _replyForwardPreviewCancel.hide(); if (!_field.isHidden()) { _field.hide(); update(); @@ -2550,7 +2573,7 @@ void HistoryWidget::onSend(bool ctrlShiftEnter, MsgId replyTo) { App::main()->readServerHistory(hist, false); hist->loadAround(0); - App::main()->sendPreparedText(hist, text, replyTo); + App::main()->sendPreparedText(hist, text, replyTo, _previewCancelled); setFieldText(QString()); _saveDraftText = true; @@ -2568,6 +2591,7 @@ void HistoryWidget::onSend(bool ctrlShiftEnter, MsgId replyTo) { App::main()->finishForwarding(hist); } if (replyTo < 0) cancelReply(); + if (_previewData && _previewData->pendingTill) previewCancel(); _field.setFocus(); } @@ -2591,9 +2615,13 @@ void HistoryWidget::shareContact(const PeerId &peer, const QString &phone, const PeerData *p = App::peer(peer); int32 flags = (p->input.type() == mtpc_inputPeerSelf) ? 0 : (MTPDmessage_flag_unread | MTPDmessage_flag_out); // unread, out - if (replyTo) flags |= MTPDmessage::flag_reply_to_msg_id; + int32 sendFlags = 0; + if (replyTo) { + flags |= MTPDmessage::flag_reply_to_msg_id; + sendFlags |= MTPmessages_SendMedia::flag_reply_to_msg_id; + } h->addToBack(MTP_message(MTP_int(flags), MTP_int(newId), MTP_int(MTP::authedId()), App::peerToMTP(peer), MTPint(), MTPint(), MTP_int(_replyToId), MTP_int(unixtime()), MTP_string(""), MTP_messageMediaContact(MTP_string(phone), MTP_string(fname), MTP_string(lname), MTP_int(userId)))); - h->sendRequestId = MTP::send(MTPmessages_SendMedia(p->input, MTP_int(replyTo), MTP_inputMediaContact(MTP_string(phone), MTP_string(fname), MTP_string(lname)), MTP_long(randomId)), App::main()->rpcDone(&MainWidget::sentUpdatesReceived), RPCFailHandlerPtr(), 0, 0, hist->sendRequestId); + h->sendRequestId = MTP::send(MTPmessages_SendMedia(MTP_int(sendFlags), p->input, MTP_int(replyTo), MTP_inputMediaContact(MTP_string(phone), MTP_string(fname), MTP_string(lname)), MTP_long(randomId)), App::main()->rpcDone(&MainWidget::sentUpdatesReceived), RPCFailHandlerPtr(), 0, 0, hist->sendRequestId); App::historyRegRandom(randomId, newId); @@ -2641,7 +2669,7 @@ void HistoryWidget::animShow(const QPixmap &bgAnimCache, const QPixmap &bgAnimTo _attachPhoto.hide(); _attachEmoji.hide(); _field.hide(); - _replyForwardCancel.hide(); + _replyForwardPreviewCancel.hide(); _send.hide(); a_coord = back ? anim::ivalue(-st::introSlideShift, 0) : anim::ivalue(st::introSlideShift, 0); a_alpha = anim::fvalue(0, 1); @@ -3021,7 +3049,7 @@ void HistoryWidget::updateOnlineDisplayTimer() { void HistoryWidget::onFieldResize() { _field.move(_attachDocument.x() + _attachDocument.width(), height() - _field.height() - st::sendPadding); - _replyForwardCancel.move(width() - _replyForwardCancel.width(), _field.y() - st::sendPadding - _replyForwardCancel.height()); + _replyForwardPreviewCancel.move(width() - _replyForwardPreviewCancel.width(), _field.y() - st::sendPadding - _replyForwardPreviewCancel.height()); updateListSize(); int backy = _scroll.y() + _scroll.height(); update(0, backy, width(), height() - backy); @@ -3212,7 +3240,11 @@ void HistoryWidget::onPhotoUploaded(MsgId newId, const MTPInputFile &file) { App::historyRegRandom(randomId, newId); History *hist = item->history(); MsgId replyTo = item->toHistoryReply() ? item->toHistoryReply()->replyToId() : 0; - hist->sendRequestId = MTP::send(MTPmessages_SendMedia(item->history()->peer->input, MTP_int(replyTo), MTP_inputMediaUploadedPhoto(file), MTP_long(randomId)), App::main()->rpcDone(&MainWidget::sentUpdatesReceived), App::main()->rpcFail(&MainWidget::sendPhotoFailed, randomId), 0, 0, hist->sendRequestId); + int32 sendFlags = 0; + if (replyTo) { + sendFlags |= MTPmessages_SendMedia::flag_reply_to_msg_id; + } + hist->sendRequestId = MTP::send(MTPmessages_SendMedia(MTP_int(sendFlags), item->history()->peer->input, MTP_int(replyTo), MTP_inputMediaUploadedPhoto(file), MTP_long(randomId)), App::main()->rpcDone(&MainWidget::sentUpdatesReceived), App::main()->rpcFail(&MainWidget::sendPhotoFailed, randomId), 0, 0, hist->sendRequestId); } } @@ -3248,7 +3280,11 @@ void HistoryWidget::onDocumentUploaded(MsgId newId, const MTPInputFile &file) { App::historyRegRandom(randomId, newId); History *hist = item->history(); MsgId replyTo = item->toHistoryReply() ? item->toHistoryReply()->replyToId() : 0; - hist->sendRequestId = MTP::send(MTPmessages_SendMedia(item->history()->peer->input, MTP_int(replyTo), MTP_inputMediaUploadedDocument(file, MTP_string(document->mime), _composeDocumentAttributes(document)), MTP_long(randomId)), App::main()->rpcDone(&MainWidget::sentUpdatesReceived), RPCFailHandlerPtr(), 0, 0, hist->sendRequestId); + int32 sendFlags = 0; + if (replyTo) { + sendFlags |= MTPmessages_SendMedia::flag_reply_to_msg_id; + } + hist->sendRequestId = MTP::send(MTPmessages_SendMedia(MTP_int(sendFlags), item->history()->peer->input, MTP_int(replyTo), MTP_inputMediaUploadedDocument(file, MTP_string(document->mime), _composeDocumentAttributes(document)), MTP_long(randomId)), App::main()->rpcDone(&MainWidget::sentUpdatesReceived), RPCFailHandlerPtr(), 0, 0, hist->sendRequestId); } } } @@ -3270,7 +3306,11 @@ void HistoryWidget::onThumbDocumentUploaded(MsgId newId, const MTPInputFile &fil App::historyRegRandom(randomId, newId); History *hist = item->history(); MsgId replyTo = item->toHistoryReply() ? item->toHistoryReply()->replyToId() : 0; - hist->sendRequestId = MTP::send(MTPmessages_SendMedia(item->history()->peer->input, MTP_int(replyTo), MTP_inputMediaUploadedThumbDocument(file, thumb, MTP_string(document->mime), _composeDocumentAttributes(document)), MTP_long(randomId)), App::main()->rpcDone(&MainWidget::sentUpdatesReceived), RPCFailHandlerPtr(), 0, 0, hist->sendRequestId); + int32 sendFlags = 0; + if (replyTo) { + sendFlags |= MTPmessages_SendMedia::flag_reply_to_msg_id; + } + hist->sendRequestId = MTP::send(MTPmessages_SendMedia(MTP_int(sendFlags), item->history()->peer->input, MTP_int(replyTo), MTP_inputMediaUploadedThumbDocument(file, thumb, MTP_string(document->mime), _composeDocumentAttributes(document)), MTP_long(randomId)), App::main()->rpcDone(&MainWidget::sentUpdatesReceived), RPCFailHandlerPtr(), 0, 0, hist->sendRequestId); } } } @@ -3312,7 +3352,7 @@ void HistoryWidget::resizeEvent(QResizeEvent *e) { _attachPhoto.move(_attachDocument.x(), _attachDocument.y()); _field.move(_attachDocument.x() + _attachDocument.width(), height() - _field.height() - st::sendPadding); - _replyForwardCancel.move(width() - _replyForwardCancel.width(), _field.y() - st::sendPadding - _replyForwardCancel.height()); + _replyForwardPreviewCancel.move(width() - _replyForwardPreviewCancel.width(), _field.y() - st::sendPadding - _replyForwardPreviewCancel.height()); updateListSize(); _field.resize(width() - _send.width() - _attachDocument.width() - _attachEmoji.width(), _field.height()); @@ -3382,7 +3422,7 @@ void HistoryWidget::updateListSize(int32 addToY, bool initial, bool loadedDown, } int32 newScrollHeight = height() - (hist->readyForWork() && (!histPeer->chat || !histPeer->asChat()->forbidden) ? (_field.height() + 2 * st::sendPadding) : 0); - if (_replyToId || App::main()->hasForwardingItems()) { + if (_replyToId || App::main()->hasForwardingItems() || (_previewData && _previewData->pendingTill >= 0)) { newScrollHeight -= st::replyHeight; } bool wasAtBottom = _scroll.scrollTop() + 1 > _scroll.scrollTopMax(), needResize = _scroll.width() != width() || _scroll.height() != newScrollHeight; @@ -3541,10 +3581,14 @@ void HistoryWidget::onStickerSend(DocumentData *sticker) { bool out = (histPeer->input.type() != mtpc_inputPeerSelf), unread = (histPeer->input.type() != mtpc_inputPeerSelf); int32 flags = (histPeer->input.type() != mtpc_inputPeerSelf) ? (MTPDmessage_flag_out | MTPDmessage_flag_unread) : 0; - if (_replyToId) flags |= MTPDmessage::flag_reply_to_msg_id; + int32 sendFlags = 0; + if (_replyToId) { + flags |= MTPDmessage::flag_reply_to_msg_id; + sendFlags |= MTPmessages_SendMedia::flag_reply_to_msg_id; + } hist->addToBackDocument(newId, flags, _replyToId, date(MTP_int(unixtime())), MTP::authedId(), sticker); - hist->sendRequestId = MTP::send(MTPmessages_SendMedia(histPeer->input, MTP_int(_replyToId), MTP_inputMediaDocument(MTP_inputDocument(MTP_long(sticker->id), MTP_long(sticker->access))), MTP_long(randomId)), App::main()->rpcDone(&MainWidget::sentUpdatesReceived), RPCFailHandlerPtr(), 0, 0, hist->sendRequestId); + hist->sendRequestId = MTP::send(MTPmessages_SendMedia(MTP_int(sendFlags), histPeer->input, MTP_int(_replyToId), MTP_inputMediaDocument(MTP_inputDocument(MTP_long(sticker->id), MTP_long(sticker->access))), MTP_long(randomId)), App::main()->rpcDone(&MainWidget::sentUpdatesReceived), RPCFailHandlerPtr(), 0, 0, hist->sendRequestId); App::main()->finishForwarding(hist); cancelReply(); @@ -3567,6 +3611,14 @@ void HistoryWidget::setFieldText(const QString &text) { _synthedTextUpdate = true; _field.setPlainText(text); _synthedTextUpdate = false; + + _previewCancelled = false; + _previewData = 0; + if (_previewRequest) { + MTP::cancel(_previewRequest); + _previewRequest = 0; + } + _previewLinks.clear(); } void HistoryWidget::onReplyToMessage() { @@ -3578,7 +3630,7 @@ void HistoryWidget::onReplyToMessage() { _replyTo = to; _replyToId = to->id; _replyToText.setText(st::msgFont, _replyTo->inDialogsText(), _textDlgOptions); - if (!_field.isHidden()) _replyForwardCancel.show(); + if (!_field.isHidden()) _replyForwardPreviewCancel.show(); updateReplyToName(); resizeEvent(0); update(); @@ -3594,7 +3646,9 @@ void HistoryWidget::cancelReply() { if (!_replyToId) return; _replyTo = 0; _replyToId = 0; - if (!App::main()->hasForwardingItems()) _replyForwardCancel.hide(); + if (!App::main()->hasForwardingItems()) { + if (!_previewData || _previewData->pendingTill < 0) _replyForwardPreviewCancel.hide(); + } resizeEvent(0); update(); @@ -3604,14 +3658,111 @@ void HistoryWidget::cancelReply() { } void HistoryWidget::cancelForwarding() { - _replyForwardCancel.hide(); + if (!_previewData || _previewData->pendingTill < 0) _replyForwardPreviewCancel.hide(); resizeEvent(0); update(); } -void HistoryWidget::onReplyForwardCancel() { - App::main()->cancelForwarding(); - cancelReply(); +void HistoryWidget::onReplyForwardPreviewCancel() { + if (_previewData && _previewData->pendingTill >= 0) { + _previewCancelled = true; + previewCancel(); + + _saveDraftText = true; + _saveDraftStart = getms(); + onDraftSave(); + } else { + App::main()->cancelForwarding(); + cancelReply(); + } +} + +void HistoryWidget::previewCancel() { + MTP::cancel(_previewRequest); + _previewRequest = 0; + _previewData = 0; + _previewLinks.clear(); + updatePreview(); + if (!_replyToId && !App::main()->hasForwardingItems()) _replyForwardPreviewCancel.hide(); +} + +void HistoryWidget::onPreviewParse() { + if (_previewCancelled) return; + _field.parseLinks(); +} + +void HistoryWidget::onPreviewCheck() { + if (_previewCancelled) return; + QStringList linksList = _field.linksList(); + QString newLinks = linksList.join(' '); + if (newLinks != _previewLinks) { + MTP::cancel(_previewRequest); + _previewLinks = newLinks; + if (_previewLinks.isEmpty()) { + if (_previewData && _previewData->pendingTill >= 0) previewCancel(); + } else { + PreviewCache::const_iterator i = _previewCache.constFind(_previewLinks); + if (i == _previewCache.cend()) { + _previewRequest = MTP::send(MTPmessages_GetWebPagePreview(MTP_string(_previewLinks)), rpcDone(&HistoryWidget::gotPreview, _previewLinks)); + } else if (i.value()) { + _previewData = App::webPage(i.value()); + updatePreview(); + } else { + if (_previewData && _previewData->pendingTill >= 0) previewCancel(); + } + } + } +} + +void HistoryWidget::onPreviewTimeout() { + if (_previewData && _previewData->pendingTill > 0 && !_previewLinks.isEmpty()) { + _previewRequest = MTP::send(MTPmessages_GetWebPagePreview(MTP_string(_previewLinks)), rpcDone(&HistoryWidget::gotPreview, _previewLinks)); + } +} + +void HistoryWidget::gotPreview(QString links, const MTPMessageMedia &result, mtpRequestId req) { + if (req == _previewRequest) { + _previewRequest = 0; + } + if (result.type() == mtpc_messageMediaWebPage) { + WebPageData *data = App::feedWebPage(result.c_messageMediaWebPage().vwebpage); + _previewCache.insert(links, data->id); + if (data->pendingTill > 0 && data->pendingTill <= unixtime()) { + data->pendingTill = -1; + } + if (links == _previewLinks && !_previewCancelled) { + _previewData = (data->id && data->pendingTill >= 0) ? data : 0; + updatePreview(); + } + } else if (result.type() == mtpc_messageMediaEmpty) { + _previewCache.insert(links, 0); + if (links == _previewLinks && !_previewCancelled) { + _previewData = 0; + updatePreview(); + } + } +} + +void HistoryWidget::updatePreview() { + _previewTimer.stop(); + if (_previewData && _previewData->pendingTill >= 0) { + _replyForwardPreviewCancel.show(); + if (_previewData->pendingTill) { + _previewTitle.setText(st::msgServiceNameFont, lang(lng_preview_loading), _textNameOptions); + _previewDescription.setText(st::msgFont, _previewLinks.splitRef(' ').at(0).toString(), _textDlgOptions); + + int32 t = (_previewData->pendingTill - unixtime()) * 1000; + if (t <= 0) t = 1; + _previewTimer.start(t); + } else { + _previewTitle.setText(st::msgServiceNameFont, _previewData->siteName, _textNameOptions); + _previewDescription.setText(st::msgFont, _previewData->title.isEmpty() ? (_previewData->description.isEmpty() ? (_previewData->author.isEmpty() ? _previewData->url : _previewData->author) : _previewData->description) : _previewData->title, _textDlgOptions); + } + } else if (!App::main()->hasForwardingItems() && !_replyToId) { + _replyForwardPreviewCancel.hide(); + } + resizeEvent(0); + update(); } void HistoryWidget::onCancel() { @@ -3732,7 +3883,7 @@ void HistoryWidget::updateTopBarSelection() { App::main()->topBar()->showSelected(_selCount > 0 ? _selCount : 0); updateControlsVisibility(); updateListSize(); - if (!App::wnd()->layerShown()) { + if (!App::wnd()->layerShown() && !App::passcoded()) { if (_selCount) { _list->setFocus(); } else { @@ -3748,7 +3899,7 @@ void HistoryWidget::updateReplyTo(bool force) { _replyTo = App::histItemById(_replyToId); if (_replyTo) { _replyToText.setText(st::msgFont, _replyTo->inDialogsText(), _textDlgOptions); - if (!_field.isHidden()) _replyForwardCancel.show(); + if (!_field.isHidden()) _replyForwardPreviewCancel.show(); updateReplyToName(); int backy = _scroll.y() + _scroll.height(); update(0, backy, width(), height() - backy); @@ -3759,7 +3910,7 @@ void HistoryWidget::updateReplyTo(bool force) { void HistoryWidget::updateForwarding(bool force) { if (App::main()->hasForwardingItems()) { - _replyForwardCancel.show(); + _replyForwardPreviewCancel.show(); } resizeEvent(0); update(); @@ -3786,51 +3937,80 @@ void HistoryWidget::drawFieldBackground(QPainter &p) { App::main()->fillForwardingInfo(from, text, serviceColor, preview); backy -= st::replyHeight; backh += st::replyHeight; + } else if (_previewData && _previewData->pendingTill >= 0) { + backy -= st::replyHeight; + backh += st::replyHeight; } + bool drawPreview = (_previewData && _previewData->pendingTill >= 0); p.fillRect(0, backy, width(), backh, st::taMsgField.bgColor->b); if (_replyToId) { int32 replyLeft = st::replySkip; p.drawPixmap(QPoint(st::replyIconPos.x(), backy + st::replyIconPos.y()), App::sprite(), st::replyIcon); - if (_replyTo) { - if (_replyTo->getMedia() && _replyTo->getMedia()->hasReplyPreview()) { - ImagePtr replyPreview = _replyTo->getMedia()->replyPreview(); - if (!replyPreview->isNull()) { - QRect to(replyLeft, backy + st::msgReplyPadding.top(), st::msgReplyBarSize.height(), st::msgReplyBarSize.height()); - if (replyPreview->width() == replyPreview->height()) { - p.drawPixmap(to.x(), to.y(), replyPreview->pix()); - } else { - QRect from = (replyPreview->width() > replyPreview->height()) ? QRect((replyPreview->width() - replyPreview->height()) / 2, 0, replyPreview->height(), replyPreview->height()) : QRect(0, (replyPreview->height() - replyPreview->width()) / 2, replyPreview->width(), replyPreview->width()); - p.drawPixmap(to, replyPreview->pix(), from); + if (!drawPreview) { + if (_replyTo) { + if (_replyTo->getMedia() && _replyTo->getMedia()->hasReplyPreview()) { + ImagePtr replyPreview = _replyTo->getMedia()->replyPreview(); + if (!replyPreview->isNull()) { + QRect to(replyLeft, backy + st::msgReplyPadding.top(), st::msgReplyBarSize.height(), st::msgReplyBarSize.height()); + if (replyPreview->width() == replyPreview->height()) { + p.drawPixmap(to.x(), to.y(), replyPreview->pix()); + } else { + QRect from = (replyPreview->width() > replyPreview->height()) ? QRect((replyPreview->width() - replyPreview->height()) / 2, 0, replyPreview->height(), replyPreview->height()) : QRect(0, (replyPreview->height() - replyPreview->width()) / 2, replyPreview->width(), replyPreview->width()); + p.drawPixmap(to, replyPreview->pix(), from); + } } + replyLeft += st::msgReplyBarSize.height() + st::msgReplyBarSkip - st::msgReplyBarSize.width() - st::msgReplyBarPos.x(); } - replyLeft += st::msgReplyBarSize.height() + st::msgReplyBarSkip - st::msgReplyBarSize.width() - st::msgReplyBarPos.x(); + p.setPen(st::replyColor->p); + _replyToName.drawElided(p, replyLeft, backy + st::msgReplyPadding.top(), width() - replyLeft - _replyForwardPreviewCancel.width() - st::msgReplyPadding.right()); + p.setPen(((_replyTo->getMedia() || _replyTo->serviceMsg()) ? st::msgInDateColor : st::msgColor)->p); + _replyToText.drawElided(p, replyLeft, backy + st::msgReplyPadding.top() + st::msgServiceNameFont->height, width() - replyLeft - _replyForwardPreviewCancel.width() - st::msgReplyPadding.right()); + } else { + p.setFont(st::msgDateFont->f); + p.setPen(st::msgInDateColor->p); + p.drawText(replyLeft, backy + st::msgReplyPadding.top() + (st::msgReplyBarSize.height() - st::msgDateFont->height) / 2 + st::msgDateFont->ascent, st::msgDateFont->m.elidedText(lang(lng_profile_loading), Qt::ElideRight, width() - replyLeft - _replyForwardPreviewCancel.width() - st::msgReplyPadding.right())); } - p.setPen(st::replyColor->p); - _replyToName.drawElided(p, replyLeft, backy + st::msgReplyPadding.top(), width() - replyLeft - _replyForwardCancel.width() - st::msgReplyPadding.right()); - p.setPen(((_replyTo->getMedia() || _replyTo->serviceMsg()) ? st::msgInDateColor : st::msgColor)->p); - _replyToText.drawElided(p, replyLeft, backy + st::msgReplyPadding.top() + st::msgServiceNameFont->height, width() - replyLeft - _replyForwardCancel.width() - st::msgReplyPadding.right()); - } else { - p.setFont(st::msgDateFont->f); - p.setPen(st::msgInDateColor->p); - p.drawText(replyLeft, backy + st::msgReplyPadding.top() + (st::msgReplyBarSize.height() - st::msgDateFont->height) / 2 + st::msgDateFont->ascent, st::msgDateFont->m.elidedText(lang(lng_profile_loading), Qt::ElideRight, width() - replyLeft - _replyForwardCancel.width() - st::msgReplyPadding.right())); } } else if (from && text) { int32 forwardLeft = st::replySkip; p.drawPixmap(QPoint(st::replyIconPos.x(), backy + st::replyIconPos.y()), App::sprite(), st::forwardIcon); - if (!preview->isNull()) { - QRect to(forwardLeft, backy + st::msgReplyPadding.top(), st::msgReplyBarSize.height(), st::msgReplyBarSize.height()); - if (preview->width() == preview->height()) { - p.drawPixmap(to.x(), to.y(), preview->pix()); - } else { - QRect from = (preview->width() > preview->height()) ? QRect((preview->width() - preview->height()) / 2, 0, preview->height(), preview->height()) : QRect(0, (preview->height() - preview->width()) / 2, preview->width(), preview->width()); - p.drawPixmap(to, preview->pix(), from); + if (!drawPreview) { + if (!preview->isNull()) { + QRect to(forwardLeft, backy + st::msgReplyPadding.top(), st::msgReplyBarSize.height(), st::msgReplyBarSize.height()); + if (preview->width() == preview->height()) { + p.drawPixmap(to.x(), to.y(), preview->pix()); + } else { + QRect from = (preview->width() > preview->height()) ? QRect((preview->width() - preview->height()) / 2, 0, preview->height(), preview->height()) : QRect(0, (preview->height() - preview->width()) / 2, preview->width(), preview->width()); + p.drawPixmap(to, preview->pix(), from); + } + forwardLeft += st::msgReplyBarSize.height() + st::msgReplyBarSkip - st::msgReplyBarSize.width() - st::msgReplyBarPos.x(); } - forwardLeft += st::msgReplyBarSize.height() + st::msgReplyBarSkip - st::msgReplyBarSize.width() - st::msgReplyBarPos.x(); + p.setPen(st::replyColor->p); + from->drawElided(p, forwardLeft, backy + st::msgReplyPadding.top(), width() - forwardLeft - _replyForwardPreviewCancel.width() - st::msgReplyPadding.right()); + p.setPen((serviceColor ? st::msgInDateColor : st::msgColor)->p); + text->drawElided(p, forwardLeft, backy + st::msgReplyPadding.top() + st::msgServiceNameFont->height, width() - forwardLeft - _replyForwardPreviewCancel.width() - st::msgReplyPadding.right()); + } + } + if (drawPreview) { + int32 previewLeft = st::replySkip + st::webPageLeft; + p.fillRect(st::replySkip, backy + st::msgReplyPadding.top(), st::webPageBar, st::msgReplyBarSize.height(), st::msgInReplyBarColor->b); + if (_previewData->photo && !_previewData->photo->thumb->isNull()) { + ImagePtr replyPreview = _previewData->photo->makeReplyPreview(); + if (!replyPreview->isNull()) { + QRect to(previewLeft, backy + st::msgReplyPadding.top(), st::msgReplyBarSize.height(), st::msgReplyBarSize.height()); + if (replyPreview->width() == replyPreview->height()) { + p.drawPixmap(to.x(), to.y(), replyPreview->pix()); + } else { + QRect from = (replyPreview->width() > replyPreview->height()) ? QRect((replyPreview->width() - replyPreview->height()) / 2, 0, replyPreview->height(), replyPreview->height()) : QRect(0, (replyPreview->height() - replyPreview->width()) / 2, replyPreview->width(), replyPreview->width()); + p.drawPixmap(to, replyPreview->pix(), from); + } + } + previewLeft += st::msgReplyBarSize.height() + st::msgReplyBarSkip - st::msgReplyBarSize.width() - st::msgReplyBarPos.x(); } p.setPen(st::replyColor->p); - from->drawElided(p, forwardLeft, backy + st::msgReplyPadding.top(), width() - forwardLeft - _replyForwardCancel.width() - st::msgReplyPadding.right()); - p.setPen((serviceColor ? st::msgInDateColor : st::msgColor)->p); - text->drawElided(p, forwardLeft, backy + st::msgReplyPadding.top() + st::msgServiceNameFont->height, width() - forwardLeft - _replyForwardCancel.width() - st::msgReplyPadding.right()); + _previewTitle.drawElided(p, previewLeft, backy + st::msgReplyPadding.top(), width() - previewLeft - _replyForwardPreviewCancel.width() - st::msgReplyPadding.right()); + p.setPen(st::msgColor->p); + _previewDescription.drawElided(p, previewLeft, backy + st::msgReplyPadding.top() + st::msgServiceNameFont->height, width() - previewLeft - _replyForwardPreviewCancel.width() - st::msgReplyPadding.right()); } } diff --git a/Telegram/SourceFiles/historywidget.h b/Telegram/SourceFiles/historywidget.h index 8ae2353e89..bc241c0e9b 100644 --- a/Telegram/SourceFiles/historywidget.h +++ b/Telegram/SourceFiles/historywidget.h @@ -363,6 +363,9 @@ public: void setReplyReturns(PeerId peer, const QList &replyReturns); void calcNextReplyReturn(); + void updatePreview(); + void previewCancel(); + ~HistoryWidget(); signals: @@ -374,7 +377,11 @@ public slots: void onCancel(); void onReplyToMessage(); - void onReplyForwardCancel(); + void onReplyForwardPreviewCancel(); + + void onPreviewParse(); + void onPreviewCheck(); + void onPreviewTimeout(); void peerUpdated(PeerData *data); void onPeerLoaded(PeerData *data); @@ -439,10 +446,20 @@ private: HistoryItem *_replyTo; Text _replyToName, _replyToText; int32 _replyToNameVersion; - IconedButton _replyForwardCancel; + IconedButton _replyForwardPreviewCancel; void updateReplyToName(); void drawFieldBackground(QPainter &p); + QString _previewLinks; + WebPageData *_previewData; + typedef QMap PreviewCache; + PreviewCache _previewCache; + mtpRequestId _previewRequest; + Text _previewTitle, _previewDescription; + SingleTimer _previewTimer; + bool _previewCancelled; + void gotPreview(QString links, const MTPMessageMedia &media, mtpRequestId req); + HistoryItem *_replyReturn; QList _replyReturns; @@ -457,7 +474,7 @@ private: uint64 _lastStickersUpdate; mtpRequestId _stickersUpdateRequest; - void writeDraft(MsgId *replyTo = 0, const QString *text = 0, const MessageCursor *cursor = 0); + void writeDraft(MsgId *replyTo = 0, const QString *text = 0, const MessageCursor *cursor = 0, bool *previewCancelled = 0); void setFieldText(const QString &text); QStringList getMediasFromMime(const QMimeData *d); diff --git a/Telegram/SourceFiles/localstorage.cpp b/Telegram/SourceFiles/localstorage.cpp index d382a83b6b..0c8744b262 100644 --- a/Telegram/SourceFiles/localstorage.cpp +++ b/Telegram/SourceFiles/localstorage.cpp @@ -1777,7 +1777,7 @@ namespace Local { _writeMap(WriteMapFast); } EncryptedDescriptor data(sizeof(quint64) + _stringSize(draft.text) + sizeof(qint32)); - data.stream << quint64(peer) << draft.text << qint32(draft.replyTo); + data.stream << quint64(peer) << draft.text << qint32(draft.replyTo) << qint32(draft.previewCancelled ? 1 : 0); FileWriteDescriptor file(i.value()); file.writeEncrypted(data); @@ -1801,10 +1801,11 @@ namespace Local { quint64 draftPeer; QString draftText; - qint32 draftReplyTo = 0; + qint32 draftReplyTo = 0, draftPreviewCancelled = 0; draft.stream >> draftPeer >> draftText; if (draft.version >= 7021) draft.stream >> draftReplyTo; - return (draftPeer == peer) ? MessageDraft(MsgId(draftReplyTo), draftText) : MessageDraft(); + if (draft.version >= 8001) draft.stream >> draftPreviewCancelled; + return (draftPeer == peer) ? MessageDraft(MsgId(draftReplyTo), draftText, (draftPreviewCancelled == 1)) : MessageDraft(); } void writeDraftPositions(const PeerId &peer, const MessageCursor &cur) { diff --git a/Telegram/SourceFiles/localstorage.h b/Telegram/SourceFiles/localstorage.h index df7fa9f757..f3a93292d6 100644 --- a/Telegram/SourceFiles/localstorage.h +++ b/Telegram/SourceFiles/localstorage.h @@ -101,10 +101,11 @@ namespace Local { int32 oldMapVersion(); struct MessageDraft { - MessageDraft(MsgId replyTo = 0, QString text = QString()) : replyTo(replyTo), text(text) { + MessageDraft(MsgId replyTo = 0, QString text = QString(), bool previewCancelled = false) : replyTo(replyTo), text(text), previewCancelled(previewCancelled) { } MsgId replyTo; QString text; + bool previewCancelled; }; void writeDraft(const PeerId &peer, const MessageDraft &draft); MessageDraft readDraft(const PeerId &peer); diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index cdaf5155ad..45f45dec9a 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -887,7 +887,7 @@ DialogsIndexed &MainWidget::contactsList() { return dialogs.contactsList(); } -void MainWidget::sendPreparedText(History *hist, const QString &text, MsgId replyTo) { +void MainWidget::sendPreparedText(History *hist, const QString &text, MsgId replyTo, bool noPreview) { saveRecentHashtags(text); QString sendingText, leftText = text; if (replyTo < 0) replyTo = history.replyToId(); @@ -899,9 +899,14 @@ void MainWidget::sendPreparedText(History *hist, const QString &text, MsgId repl MTPstring msgText(MTP_string(sendingText)); int32 flags = (hist->peer->input.type() == mtpc_inputPeerSelf) ? 0 : (MTPDmessage_flag_unread | MTPDmessage_flag_out); - if (replyTo) flags |= MTPDmessage::flag_reply_to_msg_id; + int32 sendFlags = 0; + if (replyTo) { + flags |= MTPDmessage::flag_reply_to_msg_id; + sendFlags |= MTPmessages_SendMessage::flag_reply_to_msg_id; + } + if (noPreview) sendFlags |= MTPmessages_SendMessage_flag_skipWebPage; hist->addToBack(MTP_message(MTP_int(flags), MTP_int(newId), MTP_int(MTP::authedId()), App::peerToMTP(hist->peer->id), MTPint(), MTPint(), MTP_int(replyTo), MTP_int(unixtime()), msgText, MTP_messageMediaEmpty())); - hist->sendRequestId = MTP::send(MTPmessages_SendMessage(hist->peer->input, MTP_int(replyTo), msgText, MTP_long(randomId)), App::main()->rpcDone(&MainWidget::sentDataReceived, randomId), RPCFailHandlerPtr(), 0, 0, hist->sendRequestId); + hist->sendRequestId = MTP::send(MTPmessages_SendMessage(MTP_int(sendFlags), hist->peer->input, MTP_int(replyTo), msgText, MTP_long(randomId)), App::main()->rpcDone(&MainWidget::sentDataReceived, randomId), RPCFailHandlerPtr(), 0, 0, hist->sendRequestId); } finishForwarding(hist); @@ -2899,6 +2904,7 @@ void MainWidget::feedUpdate(const MTPUpdate &update) { itemResized(j.key()); } } + history.updatePreview(); } break; case mtpc_updateDeleteMessages: { diff --git a/Telegram/SourceFiles/mainwidget.h b/Telegram/SourceFiles/mainwidget.h index 7e6e7fcbee..7fb9198ef9 100644 --- a/Telegram/SourceFiles/mainwidget.h +++ b/Telegram/SourceFiles/mainwidget.h @@ -283,7 +283,7 @@ public: DialogsIndexed &contactsList(); void sendMessage(History *history, const QString &text, MsgId replyTo); - void sendPreparedText(History *hist, const QString &text, MsgId replyTo); + void sendPreparedText(History *hist, const QString &text, MsgId replyTo, bool noPreview = false); void saveRecentHashtags(const QString &text); void readServerHistory(History *history, bool force = true); diff --git a/Telegram/SourceFiles/mtproto/generate.py b/Telegram/SourceFiles/mtproto/generate.py index cf41468b64..2b8742aaaa 100644 --- a/Telegram/SourceFiles/mtproto/generate.py +++ b/Telegram/SourceFiles/mtproto/generate.py @@ -210,7 +210,10 @@ with open('scheme.tl') as f: size = []; for k in prmsList: v = prms[k]; - size.append('v' + k + '.innerLength()'); + if (k in conditions.keys()): + size.append('(has_' + k + '() ? v' + k + '.innerLength() : 0)'); + else: + size.append('v' + k + '.innerLength()'); if (not len(size)): size.append('0'); funcsText += '\t\treturn ' + ' + '.join(size) + ';\n'; diff --git a/Telegram/SourceFiles/mtproto/mtpConnection.h b/Telegram/SourceFiles/mtproto/mtpConnection.h index 9d64de4bf1..9f0b63341a 100644 --- a/Telegram/SourceFiles/mtproto/mtpConnection.h +++ b/Telegram/SourceFiles/mtproto/mtpConnection.h @@ -24,6 +24,7 @@ enum { MTPDmessage_flag_unread = (1 << 0), MTPDmessage_flag_out = (1 << 1), MTPDmessage_flag_notify_by_from = (1 << 4), + MTPmessages_SendMessage_flag_skipWebPage = (1 << 1), }; #include "mtproto/mtpPublicRSA.h" diff --git a/Telegram/SourceFiles/mtproto/mtpScheme.cpp b/Telegram/SourceFiles/mtproto/mtpScheme.cpp index 0fe6a5050c..380e387761 100644 --- a/Telegram/SourceFiles/mtproto/mtpScheme.cpp +++ b/Telegram/SourceFiles/mtproto/mtpScheme.cpp @@ -5135,10 +5135,11 @@ void mtpTextSerializeType(MTPStringLogger &to, const mtpPrime *&from, const mtpP to.add("\n").addSpaces(lev); } switch (stage) { - case 0: to.add(" peer: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 1: to.add(" reply_to_msg_id: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 2: to.add(" message: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 3: to.add(" random_id: "); ++stages.back(); types.push_back(mtpc_long); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 0: to.add(" flags: "); ++stages.back(); if (start >= end) throw Exception("start >= end in flags"); else flags.back() = *start; types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 1: to.add(" peer: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 2: to.add(" reply_to_msg_id: "); ++stages.back(); if (flag & MTPmessages_sendMessage::flag_reply_to_msg_id) { types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 0 IN FIELD flags ]"); } break; + case 3: to.add(" message: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 4: to.add(" random_id: "); ++stages.back(); types.push_back(mtpc_long); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; } break; @@ -5151,10 +5152,11 @@ void mtpTextSerializeType(MTPStringLogger &to, const mtpPrime *&from, const mtpP to.add("\n").addSpaces(lev); } switch (stage) { - case 0: to.add(" peer: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 1: to.add(" reply_to_msg_id: "); ++stages.back(); types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 2: to.add(" media: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; - case 3: to.add(" random_id: "); ++stages.back(); types.push_back(mtpc_long); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 0: to.add(" flags: "); ++stages.back(); if (start >= end) throw Exception("start >= end in flags"); else flags.back() = *start; types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 1: to.add(" peer: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 2: to.add(" reply_to_msg_id: "); ++stages.back(); if (flag & MTPmessages_sendMedia::flag_reply_to_msg_id) { types.push_back(mtpc_int); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); } else { to.add("[ SKIPPED BY BIT 0 IN FIELD flags ]"); } break; + case 3: to.add(" media: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + case 4: to.add(" random_id: "); ++stages.back(); types.push_back(mtpc_long); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; } break; @@ -5790,6 +5792,19 @@ void mtpTextSerializeType(MTPStringLogger &to, const mtpPrime *&from, const mtpP } break; + case mtpc_messages_getWebPagePreview: + if (stage) { + to.add(",\n").addSpaces(lev); + } else { + to.add("{ messages_getWebPagePreview"); + to.add("\n").addSpaces(lev); + } + switch (stage) { + case 0: to.add(" message: "); ++stages.back(); types.push_back(mtpc_string); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break; + default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; + } + break; + case mtpc_account_getAuthorizations: to.add("{ account_getAuthorizations }"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break; diff --git a/Telegram/SourceFiles/mtproto/mtpScheme.h b/Telegram/SourceFiles/mtproto/mtpScheme.h index aa5558f88d..691d272641 100644 --- a/Telegram/SourceFiles/mtproto/mtpScheme.h +++ b/Telegram/SourceFiles/mtproto/mtpScheme.h @@ -413,8 +413,8 @@ enum { mtpc_messages_deleteMessages = 0xa5f18925, mtpc_messages_receivedMessages = 0x28abcb68, mtpc_messages_setTyping = 0xa3825e50, - mtpc_messages_sendMessage = 0x1ca852a1, - mtpc_messages_sendMedia = 0x33f6d58c, + mtpc_messages_sendMessage = 0x9add8f26, + mtpc_messages_sendMedia = 0x2d7923b1, mtpc_messages_forwardMessages = 0x55e1728d, mtpc_messages_getChats = 0x3c6aa187, mtpc_messages_getFullChat = 0x3b831c66, @@ -480,6 +480,7 @@ enum { mtpc_messages_getStickers = 0xae22e045, mtpc_messages_getAllStickers = 0xaa3bc868, mtpc_account_updateDeviceLocked = 0x38df3532, + mtpc_messages_getWebPagePreview = 0x25223e24, mtpc_account_getAuthorizations = 0xe320c158, mtpc_account_resetAuthorization = 0xdf77f3bc, mtpc_account_getPassword = 0x548a30f5, @@ -8511,14 +8512,14 @@ public: MTPMessageMedia vmedia; enum { - flag_fwd_date = (1 << 2), flag_reply_to_msg_id = (1 << 3), flag_fwd_from_id = (1 << 2), + flag_fwd_date = (1 << 2), }; - bool has_fwd_date() const { return vflags.v & flag_fwd_date; } bool has_reply_to_msg_id() const { return vflags.v & flag_reply_to_msg_id; } bool has_fwd_from_id() const { return vflags.v & flag_fwd_from_id; } + bool has_fwd_date() const { return vflags.v & flag_fwd_date; } }; class MTPDmessageService : public mtpDataImpl { @@ -9597,14 +9598,14 @@ public: MTPint vreply_to_msg_id; enum { - flag_fwd_date = (1 << 2), flag_reply_to_msg_id = (1 << 3), flag_fwd_from_id = (1 << 2), + flag_fwd_date = (1 << 2), }; - bool has_fwd_date() const { return vflags.v & flag_fwd_date; } bool has_reply_to_msg_id() const { return vflags.v & flag_reply_to_msg_id; } bool has_fwd_from_id() const { return vflags.v & flag_fwd_from_id; } + bool has_fwd_date() const { return vflags.v & flag_fwd_date; } }; class MTPDupdateShortChatMessage : public mtpDataImpl { @@ -9627,14 +9628,14 @@ public: MTPint vreply_to_msg_id; enum { - flag_fwd_date = (1 << 2), flag_reply_to_msg_id = (1 << 3), flag_fwd_from_id = (1 << 2), + flag_fwd_date = (1 << 2), }; - bool has_fwd_date() const { return vflags.v & flag_fwd_date; } bool has_reply_to_msg_id() const { return vflags.v & flag_reply_to_msg_id; } bool has_fwd_from_id() const { return vflags.v & flag_fwd_from_id; } + bool has_fwd_date() const { return vflags.v & flag_fwd_date; } }; class MTPDupdateShort : public mtpDataImpl { @@ -10439,30 +10440,30 @@ public: MTPstring vauthor; enum { + flag_description = (1 << 3), + flag_site_name = (1 << 1), + flag_title = (1 << 2), + flag_author = (1 << 8), flag_embed_height = (1 << 6), + flag_embed_url = (1 << 5), + flag_embed_width = (1 << 6), + flag_type = (1 << 0), + flag_photo = (1 << 4), flag_embed_type = (1 << 5), flag_duration = (1 << 7), - flag_photo = (1 << 4), - flag_embed_url = (1 << 5), - flag_author = (1 << 8), - flag_description = (1 << 3), - flag_type = (1 << 0), - flag_title = (1 << 2), - flag_embed_width = (1 << 6), - flag_site_name = (1 << 1), }; + bool has_description() const { return vflags.v & flag_description; } + bool has_site_name() const { return vflags.v & flag_site_name; } + bool has_title() const { return vflags.v & flag_title; } + bool has_author() const { return vflags.v & flag_author; } bool has_embed_height() const { return vflags.v & flag_embed_height; } + bool has_embed_url() const { return vflags.v & flag_embed_url; } + bool has_embed_width() const { return vflags.v & flag_embed_width; } + bool has_type() const { return vflags.v & flag_type; } + bool has_photo() const { return vflags.v & flag_photo; } bool has_embed_type() const { return vflags.v & flag_embed_type; } bool has_duration() const { return vflags.v & flag_duration; } - bool has_photo() const { return vflags.v & flag_photo; } - bool has_embed_url() const { return vflags.v & flag_embed_url; } - bool has_author() const { return vflags.v & flag_author; } - bool has_description() const { return vflags.v & flag_description; } - bool has_type() const { return vflags.v & flag_type; } - bool has_title() const { return vflags.v & flag_title; } - bool has_embed_width() const { return vflags.v & flag_embed_width; } - bool has_site_name() const { return vflags.v & flag_site_name; } }; class MTPDauthorization : public mtpDataImpl { @@ -10546,16 +10547,16 @@ public: MTPstring vemail; enum { - flag_new_salt = (1 << 0), + flag_email = (1 << 1), flag_new_password_hash = (1 << 0), flag_hint = (1 << 0), - flag_email = (1 << 1), + flag_new_salt = (1 << 0), }; - bool has_new_salt() const { return vflags.v & flag_new_salt; } + bool has_email() const { return vflags.v & flag_email; } bool has_new_password_hash() const { return vflags.v & flag_new_password_hash; } bool has_hint() const { return vflags.v & flag_hint; } - bool has_email() const { return vflags.v & flag_email; } + bool has_new_salt() const { return vflags.v & flag_new_salt; } }; class MTPDauth_passwordRecovery : public mtpDataImpl { @@ -12723,6 +12724,7 @@ public: class MTPmessages_sendMessage { // RPC method 'messages.sendMessage' public: + MTPint vflags; MTPInputPeer vpeer; MTPint vreply_to_msg_id; MTPstring vmessage; @@ -12733,24 +12735,32 @@ public: MTPmessages_sendMessage(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_messages_sendMessage) { read(from, end, cons); } - MTPmessages_sendMessage(const MTPInputPeer &_peer, MTPint _reply_to_msg_id, const MTPstring &_message, const MTPlong &_random_id) : vpeer(_peer), vreply_to_msg_id(_reply_to_msg_id), vmessage(_message), vrandom_id(_random_id) { + MTPmessages_sendMessage(MTPint _flags, const MTPInputPeer &_peer, MTPint _reply_to_msg_id, const MTPstring &_message, const MTPlong &_random_id) : vflags(_flags), vpeer(_peer), vreply_to_msg_id(_reply_to_msg_id), vmessage(_message), vrandom_id(_random_id) { } + enum { + flag_reply_to_msg_id = (1 << 0), + }; + + bool has_reply_to_msg_id() const { return vflags.v & flag_reply_to_msg_id; } + uint32 innerLength() const { - return vpeer.innerLength() + vreply_to_msg_id.innerLength() + vmessage.innerLength() + vrandom_id.innerLength(); + return vflags.innerLength() + vpeer.innerLength() + (has_reply_to_msg_id() ? vreply_to_msg_id.innerLength() : 0) + vmessage.innerLength() + vrandom_id.innerLength(); } mtpTypeId type() const { return mtpc_messages_sendMessage; } void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_messages_sendMessage) { + vflags.read(from, end); vpeer.read(from, end); - vreply_to_msg_id.read(from, end); + if (has_reply_to_msg_id()) { vreply_to_msg_id.read(from, end); } else { vreply_to_msg_id = MTPint(); } vmessage.read(from, end); vrandom_id.read(from, end); } void write(mtpBuffer &to) const { + vflags.write(to); vpeer.write(to); - vreply_to_msg_id.write(to); + if (has_reply_to_msg_id()) vreply_to_msg_id.write(to); vmessage.write(to); vrandom_id.write(to); } @@ -12765,12 +12775,13 @@ public: } MTPmessages_SendMessage(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = 0) : MTPBoxed(from, end, cons) { } - MTPmessages_SendMessage(const MTPInputPeer &_peer, MTPint _reply_to_msg_id, const MTPstring &_message, const MTPlong &_random_id) : MTPBoxed(MTPmessages_sendMessage(_peer, _reply_to_msg_id, _message, _random_id)) { + MTPmessages_SendMessage(MTPint _flags, const MTPInputPeer &_peer, MTPint _reply_to_msg_id, const MTPstring &_message, const MTPlong &_random_id) : MTPBoxed(MTPmessages_sendMessage(_flags, _peer, _reply_to_msg_id, _message, _random_id)) { } }; class MTPmessages_sendMedia { // RPC method 'messages.sendMedia' public: + MTPint vflags; MTPInputPeer vpeer; MTPint vreply_to_msg_id; MTPInputMedia vmedia; @@ -12781,24 +12792,32 @@ public: MTPmessages_sendMedia(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_messages_sendMedia) { read(from, end, cons); } - MTPmessages_sendMedia(const MTPInputPeer &_peer, MTPint _reply_to_msg_id, const MTPInputMedia &_media, const MTPlong &_random_id) : vpeer(_peer), vreply_to_msg_id(_reply_to_msg_id), vmedia(_media), vrandom_id(_random_id) { + MTPmessages_sendMedia(MTPint _flags, const MTPInputPeer &_peer, MTPint _reply_to_msg_id, const MTPInputMedia &_media, const MTPlong &_random_id) : vflags(_flags), vpeer(_peer), vreply_to_msg_id(_reply_to_msg_id), vmedia(_media), vrandom_id(_random_id) { } + enum { + flag_reply_to_msg_id = (1 << 0), + }; + + bool has_reply_to_msg_id() const { return vflags.v & flag_reply_to_msg_id; } + uint32 innerLength() const { - return vpeer.innerLength() + vreply_to_msg_id.innerLength() + vmedia.innerLength() + vrandom_id.innerLength(); + return vflags.innerLength() + vpeer.innerLength() + (has_reply_to_msg_id() ? vreply_to_msg_id.innerLength() : 0) + vmedia.innerLength() + vrandom_id.innerLength(); } mtpTypeId type() const { return mtpc_messages_sendMedia; } void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_messages_sendMedia) { + vflags.read(from, end); vpeer.read(from, end); - vreply_to_msg_id.read(from, end); + if (has_reply_to_msg_id()) { vreply_to_msg_id.read(from, end); } else { vreply_to_msg_id = MTPint(); } vmedia.read(from, end); vrandom_id.read(from, end); } void write(mtpBuffer &to) const { + vflags.write(to); vpeer.write(to); - vreply_to_msg_id.write(to); + if (has_reply_to_msg_id()) vreply_to_msg_id.write(to); vmedia.write(to); vrandom_id.write(to); } @@ -12813,7 +12832,7 @@ public: } MTPmessages_SendMedia(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = 0) : MTPBoxed(from, end, cons) { } - MTPmessages_SendMedia(const MTPInputPeer &_peer, MTPint _reply_to_msg_id, const MTPInputMedia &_media, const MTPlong &_random_id) : MTPBoxed(MTPmessages_sendMedia(_peer, _reply_to_msg_id, _media, _random_id)) { + MTPmessages_SendMedia(MTPint _flags, const MTPInputPeer &_peer, MTPint _reply_to_msg_id, const MTPInputMedia &_media, const MTPlong &_random_id) : MTPBoxed(MTPmessages_sendMedia(_flags, _peer, _reply_to_msg_id, _media, _random_id)) { } }; @@ -15558,6 +15577,45 @@ public: } }; +class MTPmessages_getWebPagePreview { // RPC method 'messages.getWebPagePreview' +public: + MTPstring vmessage; + + MTPmessages_getWebPagePreview() { + } + MTPmessages_getWebPagePreview(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_messages_getWebPagePreview) { + read(from, end, cons); + } + MTPmessages_getWebPagePreview(const MTPstring &_message) : vmessage(_message) { + } + + uint32 innerLength() const { + return vmessage.innerLength(); + } + mtpTypeId type() const { + return mtpc_messages_getWebPagePreview; + } + void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_messages_getWebPagePreview) { + vmessage.read(from, end); + } + void write(mtpBuffer &to) const { + vmessage.write(to); + } + + typedef MTPMessageMedia ResponseType; +}; +class MTPmessages_GetWebPagePreview : public MTPBoxed { +public: + MTPmessages_GetWebPagePreview() { + } + MTPmessages_GetWebPagePreview(const MTPmessages_getWebPagePreview &v) : MTPBoxed(v) { + } + MTPmessages_GetWebPagePreview(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = 0) : MTPBoxed(from, end, cons) { + } + MTPmessages_GetWebPagePreview(const MTPstring &_message) : MTPBoxed(MTPmessages_getWebPagePreview(_message)) { + } +}; + class MTPaccount_getAuthorizations { // RPC method 'account.getAuthorizations' public: MTPaccount_getAuthorizations() { diff --git a/Telegram/SourceFiles/mtproto/scheme.tl b/Telegram/SourceFiles/mtproto/scheme.tl index 1b0c0c8634..0ddd39ee68 100644 --- a/Telegram/SourceFiles/mtproto/scheme.tl +++ b/Telegram/SourceFiles/mtproto/scheme.tl @@ -624,8 +624,8 @@ messages.deleteHistory#f4f8fb61 peer:InputPeer offset:int = messages.AffectedHis messages.deleteMessages#a5f18925 id:Vector = messages.AffectedMessages; messages.receivedMessages#28abcb68 max_id:int = Vector; messages.setTyping#a3825e50 peer:InputPeer action:SendMessageAction = Bool; -messages.sendMessage#1ca852a1 peer:InputPeer reply_to_msg_id:int message:string random_id:long = messages.SentMessage; -messages.sendMedia#33f6d58c peer:InputPeer reply_to_msg_id:int media:InputMedia random_id:long = Updates; +messages.sendMessage#9add8f26 flags:# peer:InputPeer reply_to_msg_id:flags.0?int message:string random_id:long = messages.SentMessage; +messages.sendMedia#2d7923b1 flags:# peer:InputPeer reply_to_msg_id:flags.0?int media:InputMedia random_id:long = Updates; messages.forwardMessages#55e1728d peer:InputPeer id:Vector random_id:Vector = Updates; messages.getChats#3c6aa187 id:Vector = messages.Chats; messages.getFullChat#3b831c66 chat_id:int = messages.ChatFull; @@ -712,6 +712,9 @@ messages.getStickers#ae22e045 emoticon:string hash:string = messages.Stickers; messages.getAllStickers#aa3bc868 hash:string = messages.AllStickers; account.updateDeviceLocked#38df3532 period:int = Bool; + +messages.getWebPagePreview#25223e24 message:string = MessageMedia; + account.getAuthorizations#e320c158 = account.Authorizations; account.resetAuthorization#df77f3bc hash:long = Bool; account.getPassword#548a30f5 = account.Password; diff --git a/Telegram/SourceFiles/structs.h b/Telegram/SourceFiles/structs.h index 4f627f340c..0b35db792f 100644 --- a/Telegram/SourceFiles/structs.h +++ b/Telegram/SourceFiles/structs.h @@ -169,6 +169,19 @@ struct PhotoData { medium->forget(); full->forget(); } + ImagePtr makeReplyPreview() { + if (replyPreview->isNull() && !thumb->isNull()) { + if (thumb->loaded()) { + int w = thumb->width(), h = thumb->height(); + if (w <= 0) w = 1; + if (h <= 0) h = 1; + replyPreview = ImagePtr(w > h ? thumb->pix(w * st::msgReplyBarSize.height() / h, st::msgReplyBarSize.height()) : thumb->pix(st::msgReplyBarSize.height()), "PNG"); + } else { + thumb->load(); + } + } + return replyPreview; + } PhotoId id; uint64 access; int32 user;