Merge branch 'master' into profile

Conflicts:
	Telegram/Telegram.vcxproj.filters
This commit is contained in:
John Preston 2016-05-16 18:45:50 +03:00
commit 6e2dea7030
66 changed files with 4666 additions and 2354 deletions

View file

@ -599,7 +599,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
"lng_forwarded_channel_via" = "Forwarded from {channel} via {inline_bot}";
"lng_forwarded_signed" = "{channel} ({user})";
"lng_in_reply_to" = "In reply to";
"lng_edited" = "Edited";
"lng_edited" = "edited";
"lng_edited_date" = "Edited: {date}";
"lng_cancel_edit_post_sure" = "Cancel editing?";
"lng_cancel_edit_post_yes" = "Yes";
@ -897,7 +897,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
"lng_new_version_wrap" = "Telegram Desktop was updated to version {version}\n\n{changes}\n\nFull version history is available here:\n{link}";
"lng_new_version_minor" = "— Bug fixes and other minor improvements";
"lng_new_version_text" = "BOTS 2.0\n\n— Introducing Bot API 2.0, the biggest update to our bot platform since June 2015.\n— Bots can now update existing messages on the fly as you interact with them.\n— New Inline keyboards with callback, 'open URL' or 'switch to inline mode' buttons help create seamless interfaces.\n— Inline bots can now send all attachments supported by Telegram (videos, music, stickers, locations, etc.).\n— Try out these sample bots to see what's coming your way soon: @music, @sticker, @youtube, @foursquare\n\nMore info: {link}";
"lng_new_version_text" = "— Edit your messages everywhere within 2 days after posting (press the up arrow button to edit your last message).\n— Mention people in groups by typing @ and selecting them from the list — even if they don't have a username.\n\nMore: {link}";
"lng_menu_insert_unicode" = "Insert Unicode control character";

View file

@ -399,7 +399,6 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
"lng_preview_loading" = "Lade Linkvorschau...";
"lng_profile_chat_unaccessible" = "Gruppe nicht verfügbar";
"lng_topbar_info" = "Info";
"lng_profile_about_section" = "Info";
"lng_profile_description_section" = "Beschreibung";
"lng_profile_settings_section" = "Einstellungen";
@ -600,6 +599,11 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
"lng_forwarded_channel_via" = "Weitergeleitet aus {channel} via {inline_bot}";
"lng_forwarded_signed" = "{channel} ({user})";
"lng_in_reply_to" = "Antwort auf";
"lng_edited" = "bearbeitet";
"lng_edited_date" = "Bearbeitet: {date}";
"lng_cancel_edit_post_sure" = "Bearbeitung abbrechen?";
"lng_cancel_edit_post_yes" = "Ja";
"lng_cancel_edit_post_no" = "Nein";
"lng_bot_share_location_unavailable" = "Teilen von Standorten ist derzeit bei Telegram Desktop nicht möglich.";
"lng_bot_inline_geo_unavailable" = "Dieser Bot braucht deinen aktuellen Standort. Die Funktion ist bei Telegram Desktop derzeit nicht verfügbar.";
@ -893,12 +897,16 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
"lng_new_version_wrap" = "Telegram Desktop wurde aktualisiert auf Version {version}\n\n{changes}\n\nGesamter Versionsverlauf:\n{link}";
"lng_new_version_minor" = "— Fehlerbehebungen und Softwareoptimierungen";
"lng_new_version_text" = "— Optische Verbesserungen (u.a. runde Profilbilder)";
"lng_new_version_text" = "— Bearbeite deine Nachrichten nachträglich (innerhalb von 2 Tagen).\n— Erwähne Leute in Gruppen - ohne das diese einen Benutzernamen haben müssen (einfach @ eingeben und aus der Liste auswählen).\n\nMehr: {link}";
"lng_menu_insert_unicode" = "Unicode-Steuerzeichen einfügen";
"lng_full_name" = "{first_name} {last_name}";
// Not used
"lng_topbar_info" = "Info";
// Wnd specific
"lng_wnd_choose_program_menu" = "Öffnen mit...";

View file

@ -399,7 +399,6 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
"lng_preview_loading" = "Obteniendo enlace...";
"lng_profile_chat_unaccessible" = "El grupo es inaccesible";
"lng_topbar_info" = "Información";
"lng_profile_about_section" = "Acerca de";
"lng_profile_description_section" = "Descripción";
"lng_profile_settings_section" = "Ajustes";
@ -600,6 +599,11 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
"lng_forwarded_channel_via" = "Reenviado desde {channel} vía {inline_bot}";
"lng_forwarded_signed" = "{channel} ({user})";
"lng_in_reply_to" = "Respondiendo a";
"lng_edited" = "editado";
"lng_edited_date" = "Editado: {date}";
"lng_cancel_edit_post_sure" = "¿Cancelar edición?";
"lng_cancel_edit_post_yes" = "Sí";
"lng_cancel_edit_post_no" = "No";
"lng_bot_share_location_unavailable" = "Lo sentimos, compartir la ubicación no está disponible actualmente en Telegram Desktop.";
"lng_bot_inline_geo_unavailable" = "Lo sentimos, este bot requiere compartir la ubicación.\nNo está disponible en Telegram Desktop.";
@ -893,12 +897,16 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
"lng_new_version_wrap" = "Telegram Desktop ha sido actualizada a la versión {version}\n\n{changes}\n\nEl historial completo está disponible aquí:\n{link}";
"lng_new_version_minor" = "— Corrección de errores y otras mejoras menores";
"lng_new_version_text" = "— Mejoras visuales";
"lng_new_version_text" = "— Edita tus mensajes, en todas partes, hasta 2 días después del envío.\n— Menciona a personas en grupos escribiendo @ y eligiéndolas desde la lista, incluso si no tienen un alias.\n\nMás: {link}";
"lng_menu_insert_unicode" = "Insertar caracteres de control Unicode";
"lng_full_name" = "{first_name} {last_name}";
// Not used
"lng_topbar_info" = "Información";
// Wnd specific
"lng_wnd_choose_program_menu" = "Elegir programa predeterminado...";

View file

@ -399,7 +399,6 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
"lng_preview_loading" = "Recupero le info del link...";
"lng_profile_chat_unaccessible" = "Gruppo non accessibile";
"lng_topbar_info" = "Info";
"lng_profile_about_section" = "Info";
"lng_profile_description_section" = "Descrizione";
"lng_profile_settings_section" = "Impostazioni";
@ -600,6 +599,11 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
"lng_forwarded_channel_via" = "Inoltrato da {channel} via {inline_bot}";
"lng_forwarded_signed" = "{channel} ({user})";
"lng_in_reply_to" = "In risposta a";
"lng_edited" = "modificato";
"lng_edited_date" = "Modificato: {date}";
"lng_cancel_edit_post_sure" = "Annullare la modifica?";
"lng_cancel_edit_post_yes" = "Sì";
"lng_cancel_edit_post_no" = "No";
"lng_bot_share_location_unavailable" = "Spiacenti, la condivisione della posizione non è al momento disponibile su Telegram Desktop.";
"lng_bot_inline_geo_unavailable" = "Spiacenti, questo bot richiede la condivisione della posizione. Non è disponibile su Telegram Desktop.";
@ -628,7 +632,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
"lng_media_auto_photo" = "Download automatico foto";
"lng_media_auto_audio" = "Download automatico messaggi vocali";
"lng_media_auto_gif" = "Download automatico GIF";
"lng_media_auto_private_chats" = "Chat";
"lng_media_auto_private_chats" = "Chat private";
"lng_media_auto_groups" = "Gruppi e canali";
"lng_media_auto_play" = "Autoriproduzione";
@ -893,12 +897,16 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
"lng_new_version_wrap" = "Telegram Desktop si è aggiornato alla versione {version}\n\n{changes}\n\nLa cronologia degli aggiornamenti è disponibile qui:\n{link}";
"lng_new_version_minor" = "— Risoluzione di problemi e altri miglioramenti minori";
"lng_new_version_text" = "— Miglioramenti visivi";
"lng_new_version_text" = "— Modifica i tuoi messaggi ovunque entro 2 giorni dall'invio.\n— Menziona le persone nei gruppi digitando @ e selezionandole dalla lista — anche se non hanno un username.\n\nPiù informazioni: {link}";
"lng_menu_insert_unicode" = "Inserisci carattere di controllo Unicode";
"lng_full_name" = "{first_name} {last_name}";
// Not used
"lng_topbar_info" = "Info";
// Wnd specific
"lng_wnd_choose_program_menu" = "Scegli programma predefinito...";

View file

@ -399,7 +399,6 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
"lng_preview_loading" = "링크 정보를 가져오는 중..";
"lng_profile_chat_unaccessible" = "그룹에 접근할 수 없습니다.";
"lng_topbar_info" = "정보";
"lng_profile_about_section" = "정보";
"lng_profile_description_section" = "설명";
"lng_profile_settings_section" = "환경설정";
@ -594,12 +593,17 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
"lng_channel_public_link_copied" = "클립보드에 링크가 복사되었습니다.";
"lng_forwarded" = "{user}로 부터 전달 받음";
"lng_forwarded" = "{user}님으로 부터 전달 받음";
"lng_forwarded_channel" = "{channel}로 부터 전달 받음";
"lng_forwarded_via" = "{inline_bot}을 {user}로 부터 전달 받음";
"lng_forwarded_channel_via" = "{inline_bot}을 {channel}로 부터 전달 받음";
"lng_forwarded_signed" = "{channel} ({user})";
"lng_in_reply_to" = "다음 유저에게 답장 :";
"lng_edited" = "수정됨";
"lng_edited_date" = "수정됨: {date}";
"lng_cancel_edit_post_sure" = "수정을 취소하겠습니까?";
"lng_cancel_edit_post_yes" = "네";
"lng_cancel_edit_post_no" = "아니요";
"lng_bot_share_location_unavailable" = "죄송합니다, 위치 공유는 텔레그램 테스크탑에서는 현재 지원을 하고 있지 않습니다.";
"lng_bot_inline_geo_unavailable" = "죄송합니다, 위치 공유는 텔레그램 테스크탑에서는 현재 지원을 하고 있지 않습니다.";
@ -893,12 +897,16 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
"lng_new_version_wrap" = "텔레그램 데스크탑은 {version} 버전으로 업데이트 되었습니다.\n\n{changes}\n\n전체 버전 히스토리는 아래에서 확인 가능합니다:\n{link}";
"lng_new_version_minor" = "— 버그 수정 및 일부 기능 향상";
"lng_new_version_text" = "— 비주얼 향상";
"lng_new_version_text" = "— 메시지 작성 후 2일내 수정 기능\n— 그룹내에서 @를 입력하고 상대방을 선택하여 멘션 기능 ㅡ 아이디가 없어도 가능\n\n자세한 설명: {link}";
"lng_menu_insert_unicode" = "유니코드 문자를 입력하세요.";
"lng_full_name" = "{last_name} {first_name}";
// Not used
"lng_topbar_info" = "정보";
// Wnd specific
"lng_wnd_choose_program_menu" = "기본 실행 프로그램을 선택해주세요..";

View file

@ -399,7 +399,6 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
"lng_preview_loading" = "Link-preview ophalen...";
"lng_profile_chat_unaccessible" = "Groep is ontoegankelijk";
"lng_topbar_info" = "Info";
"lng_profile_about_section" = "Over";
"lng_profile_description_section" = "Beschrijving";
"lng_profile_settings_section" = "Instellingen";
@ -600,6 +599,11 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
"lng_forwarded_channel_via" = "Doorgestuurd van {channel} via {inline_bot}";
"lng_forwarded_signed" = "{channel} ({user})";
"lng_in_reply_to" = "Antwoord op";
"lng_edited" = "bewerkt";
"lng_edited_date" = "Bewerkt: {date}";
"lng_cancel_edit_post_sure" = "Bewerken annuleren?";
"lng_cancel_edit_post_yes" = "Ja";
"lng_cancel_edit_post_no" = "Nee";
"lng_bot_share_location_unavailable" = "Sorry, locatie delen is nog niet beschikbaar via Telegram Desktop.";
"lng_bot_inline_geo_unavailable" = "Deze bot heeft je locatie nodig, dit is\nnog niet beschikbaar via Telegram Desktop.";
@ -893,12 +897,16 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
"lng_new_version_wrap" = "Telegram is bijgewerkt naar versie {version}\n\n{changes} \n\nVolledige versiegeschiedenis is hier te vinden:\n{link}";
"lng_new_version_minor" = "— Probleemoplossing en andere kleine verbeteringen";
"lng_new_version_text" = "— Visuele verbeteringen";
"lng_new_version_text" = "— 2 dagen om je berichten naderhand te bewerken.\n— Anderen vermelden in groepen via '@', kies daarna de persoon uit de lijst. Ook als ze geen gebruikersnaam hebben!\n\nMeer over deze update: {link}";
"lng_menu_insert_unicode" = "Unicode-besturingsteken invoegen";
"lng_full_name" = "{first_name} {last_name}";
// Not used
"lng_topbar_info" = "Info";
// Wnd specific
"lng_wnd_choose_program_menu" = "Standaardprogramma selecteren... ";

View file

@ -399,7 +399,6 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
"lng_preview_loading" = "Obtendo Informações do Link...";
"lng_profile_chat_unaccessible" = "Grupo inacessível";
"lng_topbar_info" = "Info";
"lng_profile_about_section" = "Sobre";
"lng_profile_description_section" = "Descrição";
"lng_profile_settings_section" = "Configurações";
@ -600,6 +599,11 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
"lng_forwarded_channel_via" = "Encaminhado de {channel} via {inline_bot}";
"lng_forwarded_signed" = "{channel} ({user})";
"lng_in_reply_to" = "Em resposta a";
"lng_edited" = "editado";
"lng_edited_date" = "Editado: {date}";
"lng_cancel_edit_post_sure" = "Cancelar edição?";
"lng_cancel_edit_post_yes" = "Sim";
"lng_cancel_edit_post_no" = "Não";
"lng_bot_share_location_unavailable" = "O compartilhamento de localização está atualmente indisponível no Telegram Desktop.";
"lng_bot_inline_geo_unavailable" = "Esse bot requer compartilhamento de localização\nIsso não está disponível no Telegram Desktop.";
@ -893,12 +897,16 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
"lng_new_version_wrap" = "Telegram Desktop foi atualizado para a versão {version}\n\n{changes}\n\nHistórico completo de mudanças disponível aqui:\n{link}";
"lng_new_version_minor" = "— Resolução de bugs e outras melhorias menores";
"lng_new_version_text" = "— Melhorias no visual";
"lng_new_version_text" = "— Edite suas mensagens em qualquer lugar, até 2 dias após postagem.\n— Mencione pessoas nos grupos digitando @ e selecionando-os através da lista — mesmo que eles não tenham nome de usuário.\n\nMais: {link}";
"lng_menu_insert_unicode" = "Inserir caractere de controle Unicode";
"lng_full_name" = "{first_name} {last_name}";
// Not used
"lng_topbar_info" = "Info";
// Wnd specific
"lng_wnd_choose_program_menu" = "Escolher programa padrão...";

View file

@ -34,8 +34,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 0,9,48,0
PRODUCTVERSION 0,9,48,0
FILEVERSION 0,9,49,0
PRODUCTVERSION 0,9,49,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@ -51,10 +51,10 @@ BEGIN
BLOCK "040904b0"
BEGIN
VALUE "CompanyName", "Telegram Messenger LLP"
VALUE "FileVersion", "0.9.48.0"
VALUE "FileVersion", "0.9.49.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2016"
VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "0.9.48.0"
VALUE "ProductVersion", "0.9.49.0"
END
END
BLOCK "VarFileInfo"

View file

@ -25,8 +25,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 0,9,48,0
PRODUCTVERSION 0,9,48,0
FILEVERSION 0,9,49,0
PRODUCTVERSION 0,9,49,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@ -43,10 +43,10 @@ BEGIN
BEGIN
VALUE "CompanyName", "Telegram Messenger LLP"
VALUE "FileDescription", "Telegram Updater"
VALUE "FileVersion", "0.9.48.0"
VALUE "FileVersion", "0.9.49.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2016"
VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "0.9.48.0"
VALUE "ProductVersion", "0.9.49.0"
END
END
BLOCK "VarFileInfo"

View file

@ -37,17 +37,19 @@ ApiWrap::ApiWrap(QObject *parent) : QObject(parent)
void ApiWrap::init() {
}
void ApiWrap::requestMessageData(ChannelData *channel, MsgId msgId, RequestMessageDataCallback *callback) {
MessageDataRequest::CallbackPtr pcallback(callback);
void ApiWrap::requestMessageData(ChannelData *channel, MsgId msgId, std_::unique_ptr<RequestMessageDataCallback> callback) {
MessageDataRequest &req(channel ? _channelMessageDataRequests[channel][msgId] : _messageDataRequests[msgId]);
if (callback) {
MessageDataRequest::CallbackPtr pcallback(callback.release());
req.callbacks.append(pcallback);
}
if (!req.req) _messageDataResolveDelayed->call();
}
ApiWrap::MessageIds ApiWrap::collectMessageIds(const MessageDataRequests &requests) {
MessageIds result;
result.reserve(requests.size());
for (MessageDataRequests::const_iterator i = requests.cbegin(), e = requests.cend(); i != e; ++i) {
for (auto i = requests.cbegin(), e = requests.cend(); i != e; ++i) {
if (i.value().req > 0) continue;
result.push_back(MTP_int(i.key()));
}
@ -56,7 +58,7 @@ ApiWrap::MessageIds ApiWrap::collectMessageIds(const MessageDataRequests &reques
ApiWrap::MessageDataRequests *ApiWrap::messageDataRequests(ChannelData *channel, bool onlyExisting) {
if (channel) {
ChannelMessageDataRequests::iterator i = _channelMessageDataRequests.find(channel);
auto i = _channelMessageDataRequests.find(channel);
if (i == _channelMessageDataRequests.cend()) {
if (onlyExisting) return 0;
i = _channelMessageDataRequests.insert(channel, MessageDataRequests());
@ -72,12 +74,12 @@ void ApiWrap::resolveMessageDatas() {
MessageIds ids = collectMessageIds(_messageDataRequests);
if (!ids.isEmpty()) {
mtpRequestId req = MTP::send(MTPmessages_GetMessages(MTP_vector<MTPint>(ids)), rpcDone(&ApiWrap::gotMessageDatas, (ChannelData*)nullptr), RPCFailHandlerPtr(), 0, 5);
for (MessageDataRequests::iterator i = _messageDataRequests.begin(); i != _messageDataRequests.cend(); ++i) {
if (i.value().req > 0) continue;
i.value().req = req;
for (auto &request : _messageDataRequests) {
if (request.req > 0) continue;
request.req = req;
}
}
for (ChannelMessageDataRequests::iterator j = _channelMessageDataRequests.begin(); j != _channelMessageDataRequests.cend();) {
for (auto j = _channelMessageDataRequests.begin(); j != _channelMessageDataRequests.cend();) {
if (j->isEmpty()) {
j = _channelMessageDataRequests.erase(j);
continue;
@ -85,9 +87,9 @@ void ApiWrap::resolveMessageDatas() {
MessageIds ids = collectMessageIds(j.value());
if (!ids.isEmpty()) {
mtpRequestId req = MTP::send(MTPchannels_GetMessages(j.key()->inputChannel, MTP_vector<MTPint>(ids)), rpcDone(&ApiWrap::gotMessageDatas, j.key()), RPCFailHandlerPtr(), 0, 5);
for (MessageDataRequests::iterator i = j->begin(); i != j->cend(); ++i) {
if (i.value().req > 0) continue;
i.value().req = req;
for (auto &request : *j) {
if (request.req > 0) continue;
request.req = req;
}
}
++j;
@ -128,10 +130,10 @@ void ApiWrap::gotMessageDatas(ChannelData *channel, const MTPmessages_Messages &
}
MessageDataRequests *requests(messageDataRequests(channel, true));
if (requests) {
for (MessageDataRequests::iterator i = requests->begin(); i != requests->cend();) {
for (auto i = requests->begin(); i != requests->cend();) {
if (i.value().req == req) {
for (MessageDataRequest::Callbacks::const_iterator j = i.value().callbacks.cbegin(), e = i.value().callbacks.cend(); j != e; ++j) {
(*j)->call(channel, i.key());
for_const (auto &callback, i.value().callbacks) {
callback->call(channel, i.key());
}
i = requests->erase(i);
} else {

View file

@ -29,7 +29,7 @@ public:
void init();
typedef SharedCallback<void, ChannelData*, MsgId> RequestMessageDataCallback;
void requestMessageData(ChannelData *channel, MsgId msgId, RequestMessageDataCallback *callback);
void requestMessageData(ChannelData *channel, MsgId msgId, std_::unique_ptr<RequestMessageDataCallback> callback);
void requestFullPeer(PeerData *peer);
void requestPeer(PeerData *peer);

View file

@ -968,7 +968,9 @@ namespace {
peerId = peerFromUser(m.vfrom_id);
}
if (HistoryItem *existing = App::histItemById(peerToChannel(peerId), m.vid.v)) {
existing->setText(qs(m.vmessage), m.has_entities() ? entitiesFromMTP(m.ventities.c_vector().v) : EntitiesInText());
auto text = qs(m.vmessage);
auto entities = m.has_entities() ? entitiesFromMTP(m.ventities.c_vector().v) : EntitiesInText();
existing->setText({ text, entities });
existing->updateMedia(m.has_media() ? (&m.vmedia) : nullptr);
existing->setViewsCount(m.has_views() ? m.vviews.v : -1);
existing->addToOverview(AddToOverviewNew);

View file

@ -1031,10 +1031,10 @@ void AppClass::checkMapVersion() {
if (Local::oldMapVersion() < AppVersion) {
if (Local::oldMapVersion()) {
QString versionFeatures;
if ((cAlphaVersion() || cBetaVersion()) && Local::oldMapVersion() < 9041) {
versionFeatures = QString::fromUtf8("\xe2\x80\x94 Select and copy text in photo / video captions and web page previews\n\xe2\x80\x94 Media player shortcuts are enabled only when player is opened");
// versionFeatures = langNewVersionText();
} else if (Local::oldMapVersion() < 9041) {
if ((cAlphaVersion() || cBetaVersion()) && Local::oldMapVersion() < 9049) {
// versionFeatures = QString::fromUtf8("\xe2\x80\x94 Select and copy text in photo / video captions and web page previews\n\xe2\x80\x94 Media player shortcuts are enabled only when player is opened");
versionFeatures = langNewVersionText();
} else if (Local::oldMapVersion() < 9049) {
versionFeatures = langNewVersionText();
} else {
versionFeatures = lang(lng_new_version_minor).trimmed();

View file

@ -414,7 +414,7 @@ EditCaptionBox::EditCaptionBox(HistoryItem *msg) : AbstractBox(st::boxWideWidth)
image = doc->thumb;
} break;
}
caption = media->getCaption();
caption = media->getCaption().text;
}
if ((!_animated && (dimensions.isEmpty() || doc)) || image->isNull()) {
_animated = false;
@ -492,7 +492,8 @@ EditCaptionBox::EditCaptionBox(HistoryItem *msg) : AbstractBox(st::boxWideWidth)
_field->setMaxLength(MaxPhotoCaption);
_field->setCtrlEnterSubmit(CtrlEnterSubmitBoth);
} else {
QString text = textApplyEntities(msg->originalText(), msg->originalEntities());
auto original = msg->originalText();
QString text = textApplyEntities(original.text, original.entities);
_field = new InputArea(this, st::editTextArea, lang(lng_photo_caption), text);
// _field->setMaxLength(MaxMessageSize); // entities can make text in input field larger but still valid
_field->setCtrlEnterSubmit(cCtrlEnter() ? CtrlEnterSubmitCtrlEnter : CtrlEnterSubmitEnter);

View file

@ -166,6 +166,16 @@ template <typename T, size_t N> char(&ArraySizeHelper(T(&array)[N]))[N];
#define qsl(s) QStringLiteral(s)
#define qstr(s) QLatin1String(s, sizeof(s) - 1)
// For QFlags<> declared in private section of a class we need to declare
// operators from Q_DECLARE_OPERATORS_FOR_FLAGS as friend functions.
#define Q_DECLARE_FRIEND_INCOMPATIBLE_FLAGS(Flags) \
friend Q_DECL_CONSTEXPR QIncompatibleFlag operator|(Flags::enum_type f1, int f2) Q_DECL_NOTHROW;
#define Q_DECLARE_FRIEND_OPERATORS_FOR_FLAGS(Flags) \
friend Q_DECL_CONSTEXPR QFlags<Flags::enum_type> operator|(Flags::enum_type f1, Flags::enum_type f2) Q_DECL_NOTHROW; \
friend Q_DECL_CONSTEXPR QFlags<Flags::enum_type> operator|(Flags::enum_type f1, QFlags<Flags::enum_type> f2) Q_DECL_NOTHROW; \
Q_DECLARE_FRIEND_INCOMPATIBLE_FLAGS(Flags)
// using for_const instead of plain range-based for loop to ensure usage of const_iterator
// it is important for the copy-on-write Qt containers
// if you have "QVector<T*> v" then "for (T * const p : v)" will still call QVector::detach(),

View file

@ -61,3 +61,17 @@ bool ClickHandler::setActive(const ClickHandlerPtr &p, ClickHandlerHost *host) {
}
return true;
}
QString ClickHandler::getExpandedLinkText(ExpandLinksMode mode, const QStringRef &textPart) const {
return QString();
}
TextWithEntities ClickHandler::getExpandedLinkTextWithEntities(ExpandLinksMode mode, int entityOffset, const QStringRef &textPart) const {
return { QString(), EntitiesInText() };
}
TextWithEntities ClickHandler::simpleTextWithEntity(const EntityInText &entity) const {
TextWithEntities result;
result.entities.push_back(entity);
return result;
}

View file

@ -23,6 +23,12 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
class ClickHandler;
using ClickHandlerPtr = QSharedPointer<ClickHandler>;
enum ExpandLinksMode {
ExpandLinksNone,
ExpandLinksShortened,
ExpandLinksAll,
};
class ClickHandlerHost {
protected:
@ -35,35 +41,44 @@ protected:
};
class EntityInText;
struct TextWithEntities;
class ClickHandler {
public:
virtual void onClick(Qt::MouseButton) const = 0;
virtual QString tooltip() const {
return QString();
}
virtual void copyToClipboard() const {
}
virtual QString copyToClipboardContextItem() const {
return QString();
}
virtual QString text() const {
return QString();
}
virtual QString dragText() const {
return text();
}
virtual ~ClickHandler() {
}
// this method should be called on mouse over a click handler
// it returns true if something was changed or false otherwise
virtual void onClick(Qt::MouseButton) const = 0;
// What text to show in a tooltip when mouse is over that click handler as a link in Text.
virtual QString tooltip() const {
return QString();
}
// What to drop in the input fields when dragging that click handler as a link from Text.
virtual QString dragText() const {
return QString();
}
// Copy to clipboard support.
virtual void copyToClipboard() const {
}
virtual QString copyToClipboardContextItemText() const {
return QString();
}
// Entities in text support.
// This method returns empty string if just textPart should be used (nothing to expand).
virtual QString getExpandedLinkText(ExpandLinksMode mode, const QStringRef &textPart) const;
virtual TextWithEntities getExpandedLinkTextWithEntities(ExpandLinksMode mode, int entityOffset, const QStringRef &textPart) const;
// This method should be called on mouse over a click handler.
// It returns true if the active handler was changed or false otherwise.
static bool setActive(const ClickHandlerPtr &p, ClickHandlerHost *host = nullptr);
// this method should be called when mouse leaves the host
// it returns true if something was changed or false otherwise
// This method should be called when mouse leaves the host.
// It returns true if the active handler was changed or false otherwise.
static bool clearActive(ClickHandlerHost *host = nullptr) {
if (host && _activeHost != host) {
return false;
@ -71,7 +86,7 @@ public:
return setActive(ClickHandlerPtr(), host);
}
// this method should be called on mouse pressed
// This method should be called on mouse press event.
static void pressed() {
unpressed();
if (!_active || !*_active) {
@ -84,8 +99,8 @@ public:
}
}
// this method should be called on mouse released
// the activated click handler is returned
// This method should be called on mouse release event.
// The activated click handler (if any) is returned.
static ClickHandlerPtr unpressed() {
if (_pressed && *_pressed) {
bool activated = (_active && *_active == *_pressed);
@ -136,8 +151,12 @@ public:
}
}
private:
protected:
// For click handlers like mention or hashtag in getExpandedLinkTextWithEntities()
// we return just an empty string ("use original string part") with single entity.
TextWithEntities simpleTextWithEntity(const EntityInText &entity) const;
private:
static NeverFreedPointer<ClickHandlerPtr> _active;
static NeverFreedPointer<ClickHandlerPtr> _pressed;
static ClickHandlerHost *_activeHost;

View file

@ -25,7 +25,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
#include "pspecific.h"
#include "boxes/confirmbox.h"
QString UrlClickHandler::copyToClipboardContextItem() const {
QString UrlClickHandler::copyToClipboardContextItemText() const {
return lang(isEmail() ? lng_context_copy_email : lng_context_copy_link);
}
@ -74,8 +74,28 @@ void UrlClickHandler::doOpen(QString url) {
}
}
QString UrlClickHandler::getExpandedLinkText(ExpandLinksMode mode, const QStringRef &textPart) const {
QString result;
if (mode != ExpandLinksNone) {
result = _originalUrl;
}
return result;
}
TextWithEntities UrlClickHandler::getExpandedLinkTextWithEntities(ExpandLinksMode mode, int entityOffset, const QStringRef &textPart) const {
TextWithEntities result;
auto entityType = isEmail(_originalUrl) ? EntityInTextEmail : EntityInTextUrl;
int entityLength = textPart.size();
if (mode != ExpandLinksNone) {
result.text = _originalUrl;
entityLength = _originalUrl.size();
}
result.entities.push_back({ entityType, entityOffset, entityLength });
return result;
}
void HiddenUrlClickHandler::onClick(Qt::MouseButton button) const {
QString u = url();
auto u = url();
u = tryConvertUrlToLocal(u);
@ -86,22 +106,24 @@ void HiddenUrlClickHandler::onClick(Qt::MouseButton button) const {
}
}
QString LocationClickHandler::copyToClipboardContextItem() const {
return lang(lng_context_copy_link);
}
void LocationClickHandler::onClick(Qt::MouseButton button) const {
if (!psLaunchMaps(_coords)) {
QDesktopServices::openUrl(_text);
QString HiddenUrlClickHandler::getExpandedLinkText(ExpandLinksMode mode, const QStringRef &textPart) const {
QString result;
if (mode == ExpandLinksAll) {
result = textPart.toString() + qsl(" (") + url() + ')';
}
return result;
}
void LocationClickHandler::setup() {
QString latlon(qsl("%1,%2").arg(_coords.lat).arg(_coords.lon));
_text = qsl("https://maps.google.com/maps?q=") + latlon + qsl("&ll=") + latlon + qsl("&z=16");
TextWithEntities HiddenUrlClickHandler::getExpandedLinkTextWithEntities(ExpandLinksMode mode, int entityOffset, const QStringRef &textPart) const {
TextWithEntities result;
result.entities.push_back({ EntityInTextCustomUrl, entityOffset, textPart.size(), url() });
if (mode == ExpandLinksAll) {
result.text = textPart.toString() + qsl(" (") + url() + ')';
}
return result;
}
QString MentionClickHandler::copyToClipboardContextItem() const {
QString MentionClickHandler::copyToClipboardContextItemText() const {
return lang(lng_context_copy_mention);
}
@ -111,7 +133,34 @@ void MentionClickHandler::onClick(Qt::MouseButton button) const {
}
}
QString HashtagClickHandler::copyToClipboardContextItem() const {
TextWithEntities MentionClickHandler::getExpandedLinkTextWithEntities(ExpandLinksMode mode, int entityOffset, const QStringRef &textPart) const {
return simpleTextWithEntity({ EntityInTextMention, entityOffset, textPart.size() });
}
void MentionNameClickHandler::onClick(Qt::MouseButton button) const {
if (button == Qt::LeftButton || button == Qt::MiddleButton) {
if (auto user = App::userLoaded(_userId)) {
Ui::showPeerProfile(user);
}
}
}
TextWithEntities MentionNameClickHandler::getExpandedLinkTextWithEntities(ExpandLinksMode mode, int entityOffset, const QStringRef &textPart) const {
auto data = QString::number(_userId) + '.' + QString::number(_accessHash);
return simpleTextWithEntity({ EntityInTextMentionName, entityOffset, textPart.size(), data });
}
QString MentionNameClickHandler::tooltip() const {
if (auto user = App::userLoaded(_userId)) {
auto name = App::peerName(user);
if (name != _text) {
return name;
}
}
return QString();
}
QString HashtagClickHandler::copyToClipboardContextItemText() const {
return lang(lng_context_copy_hashtag);
}
@ -121,6 +170,10 @@ void HashtagClickHandler::onClick(Qt::MouseButton button) const {
}
}
TextWithEntities HashtagClickHandler::getExpandedLinkTextWithEntities(ExpandLinksMode mode, int entityOffset, const QStringRef &textPart) const {
return simpleTextWithEntity({ EntityInTextHashtag, entityOffset, textPart.size() });
}
void BotCommandClickHandler::onClick(Qt::MouseButton button) const {
if (button == Qt::LeftButton || button == Qt::MiddleButton) {
if (PeerData *peer = Ui::getPeerForMouseAction()) {
@ -137,3 +190,7 @@ void BotCommandClickHandler::onClick(Qt::MouseButton button) const {
}
}
}
TextWithEntities BotCommandClickHandler::getExpandedLinkTextWithEntities(ExpandLinksMode mode, int entityOffset, const QStringRef &textPart) const {
return simpleTextWithEntity({ EntityInTextHashtag, entityOffset, textPart.size() });
}

View file

@ -55,23 +55,23 @@ protected:
class UrlClickHandler : public TextClickHandler {
public:
UrlClickHandler(const QString &url, bool fullDisplayed = true) : TextClickHandler(fullDisplayed), _url(url) {
UrlClickHandler(const QString &url, bool fullDisplayed = true) : TextClickHandler(fullDisplayed), _originalUrl(url) {
if (isEmail()) {
_readable = _url;
_readable = _originalUrl;
} else {
QUrl u(_url), good(u.isValid() ? u.toEncoded() : QString());
_readable = good.isValid() ? good.toDisplayString() : _url;
QUrl u(_originalUrl), good(u.isValid() ? u.toEncoded() : QString());
_readable = good.isValid() ? good.toDisplayString() : _originalUrl;
}
}
QString copyToClipboardContextItem() const override;
QString copyToClipboardContextItemText() const override;
QString text() const override {
return _url;
}
QString dragText() const override {
return url();
}
QString getExpandedLinkText(ExpandLinksMode mode, const QStringRef &textPart) const override;
TextWithEntities getExpandedLinkTextWithEntities(ExpandLinksMode mode, int entityOffset, const QStringRef &textPart) const override;
static void doOpen(QString url);
void onClick(Qt::MouseButton button) const override {
if (button == Qt::LeftButton || button == Qt::MiddleButton) {
@ -82,11 +82,11 @@ public:
protected:
QString url() const override {
if (isEmail()) {
return _url;
return _originalUrl;
}
QUrl u(_url), good(u.isValid() ? u.toEncoded() : QString());
QString result(good.isValid() ? QString::fromUtf8(good.toEncoded()) : _url);
QUrl u(_originalUrl), good(u.isValid() ? u.toEncoded() : QString());
QString result(good.isValid() ? QString::fromUtf8(good.toEncoded()) : _originalUrl);
if (!QRegularExpression(qsl("^[a-zA-Z]+:")).match(result).hasMatch()) { // no protocol
return qsl("http://") + result;
@ -103,10 +103,10 @@ private:
return ((at > 0) && (slash < 0 || slash > at));
}
bool isEmail() const {
return isEmail(_url);
return isEmail(_originalUrl);
}
QString _url, _readable;
QString _originalUrl, _readable;
};
typedef QSharedPointer<TextClickHandler> TextClickHandlerPtr;
@ -117,18 +117,25 @@ public:
}
void onClick(Qt::MouseButton button) const override;
QString getExpandedLinkText(ExpandLinksMode mode, const QStringRef &textPart) const override;
TextWithEntities getExpandedLinkTextWithEntities(ExpandLinksMode mode, int entityOffset, const QStringRef &textPart) const override;
};
class MentionClickHandler : public TextClickHandler {
public:
MentionClickHandler(const QString &tag) : _tag(tag) {
}
QString copyToClipboardContextItem() const override;
QString text() const override {
void onClick(Qt::MouseButton button) const override;
QString dragText() const override {
return _tag;
}
void onClick(Qt::MouseButton button) const override;
QString copyToClipboardContextItemText() const override;
TextWithEntities getExpandedLinkTextWithEntities(ExpandLinksMode mode, int entityOffset, const QStringRef &textPart) const override;
protected:
QString url() const override {
@ -140,16 +147,41 @@ private:
};
class MentionNameClickHandler : public ClickHandler {
public:
MentionNameClickHandler(QString text, UserId userId, uint64 accessHash)
: _text(text)
, _userId(userId)
, _accessHash(accessHash) {
}
void onClick(Qt::MouseButton button) const override;
TextWithEntities getExpandedLinkTextWithEntities(ExpandLinksMode mode, int entityOffset, const QStringRef &textPart) const override;
QString tooltip() const override;
private:
QString _text;
UserId _userId;
uint64 _accessHash;
};
class HashtagClickHandler : public TextClickHandler {
public:
HashtagClickHandler(const QString &tag) : _tag(tag) {
}
QString copyToClipboardContextItem() const override;
QString text() const override {
void onClick(Qt::MouseButton button) const override;
QString dragText() const override {
return _tag;
}
void onClick(Qt::MouseButton button) const override;
QString copyToClipboardContextItemText() const override;
TextWithEntities getExpandedLinkTextWithEntities(ExpandLinksMode mode, int entityOffset, const QStringRef &textPart) const override;
protected:
QString url() const override {
@ -165,10 +197,14 @@ class BotCommandClickHandler : public TextClickHandler {
public:
BotCommandClickHandler(const QString &cmd) : _cmd(cmd) {
}
QString text() const override {
void onClick(Qt::MouseButton button) const override;
QString dragText() const override {
return _cmd;
}
void onClick(Qt::MouseButton button) const override;
TextWithEntities getExpandedLinkTextWithEntities(ExpandLinksMode mode, int entityOffset, const QStringRef &textPart) const override;
protected:
QString url() const override {

View file

@ -24,7 +24,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
#define BETA_VERSION_MACRO (0ULL)
constexpr int AppVersion = 9048;
constexpr str_const AppVersionStr = "0.9.48";
constexpr int AppVersion = 9049;
constexpr str_const AppVersionStr = "0.9.49";
constexpr bool AppAlphaVersion = false;
constexpr uint64 AppBetaVersion = BETA_VERSION_MACRO;

View file

@ -1395,8 +1395,8 @@ void DialogsInner::selectSkipPage(int32 pixels, int32 direction) {
_sel = *i;
}
} else {
for (auto i = shownDialogs()->cfind(_sel), b = shownDialogs()->cbegin(); i != b && (toSkip--); --i) {
_sel = *i;
for (auto i = shownDialogs()->cfind(_sel), b = shownDialogs()->cbegin(); i != b && (toSkip--);) {
_sel = *(--i);
}
if (toSkip && importantDialogs) {
_importantSwitchSel = true;

View file

@ -3870,874 +3870,3 @@ void EmojiPan::recountContentMaxHeight() {
}
updateContentHeight();
}
MentionsInner::MentionsInner(MentionsDropdown *parent, MentionRows *mrows, HashtagRows *hrows, BotCommandRows *brows, StickerPack *srows)
: _parent(parent)
, _mrows(mrows)
, _hrows(hrows)
, _brows(brows)
, _srows(srows)
, _stickersPerRow(1)
, _recentInlineBotsInRows(0)
, _sel(-1)
, _down(-1)
, _mouseSel(false)
, _overDelete(false)
, _previewShown(false) {
_previewTimer.setSingleShot(true);
connect(&_previewTimer, SIGNAL(timeout()), this, SLOT(onPreview()));
}
void MentionsInner::paintEvent(QPaintEvent *e) {
Painter p(this);
QRect r(e->rect());
if (r != rect()) p.setClipRect(r);
int32 atwidth = st::mentionFont->width('@'), hashwidth = st::mentionFont->width('#');
int32 mentionleft = 2 * st::mentionPadding.left() + st::mentionPhotoSize;
int32 mentionwidth = width() - mentionleft - 2 * st::mentionPadding.right();
int32 htagleft = st::btnAttachPhoto.width + st::taMsgField.textMrg.left() - st::lineWidth, htagwidth = width() - st::mentionPadding.right() - htagleft - st::mentionScroll.width;
if (!_srows->isEmpty()) {
int32 rows = rowscount(_srows->size(), _stickersPerRow);
int32 fromrow = floorclamp(r.y() - st::stickerPanPadding, st::stickerPanSize.height(), 0, rows);
int32 torow = ceilclamp(r.y() + r.height() - st::stickerPanPadding, st::stickerPanSize.height(), 0, rows);
int32 fromcol = floorclamp(r.x() - st::stickerPanPadding, st::stickerPanSize.width(), 0, _stickersPerRow);
int32 tocol = ceilclamp(r.x() + r.width() - st::stickerPanPadding, st::stickerPanSize.width(), 0, _stickersPerRow);
for (int32 row = fromrow; row < torow; ++row) {
for (int32 col = fromcol; col < tocol; ++col) {
int32 index = row * _stickersPerRow + col;
if (index >= _srows->size()) break;
DocumentData *sticker = _srows->at(index);
if (!sticker->sticker()) continue;
QPoint pos(st::stickerPanPadding + col * st::stickerPanSize.width(), st::stickerPanPadding + row * st::stickerPanSize.height());
if (_sel == index) {
QPoint tl(pos);
if (rtl()) tl.setX(width() - tl.x() - st::stickerPanSize.width());
App::roundRect(p, QRect(tl, st::stickerPanSize), st::emojiPanHover, StickerHoverCorners);
}
bool goodThumb = !sticker->thumb->isNull() && ((sticker->thumb->width() >= 128) || (sticker->thumb->height() >= 128));
if (goodThumb) {
sticker->thumb->load();
} else {
sticker->checkSticker();
}
float64 coef = qMin((st::stickerPanSize.width() - st::msgRadius * 2) / float64(sticker->dimensions.width()), (st::stickerPanSize.height() - st::msgRadius * 2) / float64(sticker->dimensions.height()));
if (coef > 1) coef = 1;
int32 w = qRound(coef * sticker->dimensions.width()), h = qRound(coef * sticker->dimensions.height());
if (w < 1) w = 1;
if (h < 1) h = 1;
QPoint ppos = pos + QPoint((st::stickerPanSize.width() - w) / 2, (st::stickerPanSize.height() - h) / 2);
if (goodThumb) {
p.drawPixmapLeft(ppos, width(), sticker->thumb->pix(w, h));
} else if (!sticker->sticker()->img->isNull()) {
p.drawPixmapLeft(ppos, width(), sticker->sticker()->img->pix(w, h));
}
}
}
} else {
int32 from = qFloor(e->rect().top() / st::mentionHeight), to = qFloor(e->rect().bottom() / st::mentionHeight) + 1;
int32 last = _mrows->isEmpty() ? (_hrows->isEmpty() ? _brows->size() : _hrows->size()) : _mrows->size();
bool hasUsername = _parent->filter().indexOf('@') > 1;
for (int32 i = from; i < to; ++i) {
if (i >= last) break;
bool selected = (i == _sel);
if (selected) {
p.fillRect(0, i * st::mentionHeight, width(), st::mentionHeight, st::mentionBgOver->b);
int skip = (st::mentionHeight - st::notifyClose.icon.pxHeight()) / 2;
if (!_hrows->isEmpty() || (!_mrows->isEmpty() && i < _recentInlineBotsInRows)) {
p.drawSprite(QPoint(width() - st::notifyClose.icon.pxWidth() - skip, i * st::mentionHeight + skip), st::notifyClose.icon);
}
}
p.setPen(st::black->p);
if (!_mrows->isEmpty()) {
UserData *user = _mrows->at(i);
QString first = (_parent->filter().size() < 2) ? QString() : ('@' + user->username.mid(0, _parent->filter().size() - 1)), second = (_parent->filter().size() < 2) ? ('@' + user->username) : user->username.mid(_parent->filter().size() - 1);
int32 firstwidth = st::mentionFont->width(first), secondwidth = st::mentionFont->width(second), unamewidth = firstwidth + secondwidth, namewidth = user->nameText.maxWidth();
if (mentionwidth < unamewidth + namewidth) {
namewidth = (mentionwidth * namewidth) / (namewidth + unamewidth);
unamewidth = mentionwidth - namewidth;
if (firstwidth < unamewidth + st::mentionFont->elidew) {
if (firstwidth < unamewidth) {
first = st::mentionFont->elided(first, unamewidth);
} else if (!second.isEmpty()) {
first = st::mentionFont->elided(first + second, unamewidth);
second = QString();
}
} else {
second = st::mentionFont->elided(second, unamewidth - firstwidth);
}
}
user->loadUserpic();
user->paintUserpicLeft(p, st::mentionPhotoSize, st::mentionPadding.left(), i * st::mentionHeight + st::mentionPadding.top(), width());
user->nameText.drawElided(p, 2 * st::mentionPadding.left() + st::mentionPhotoSize, i * st::mentionHeight + st::mentionTop, namewidth);
p.setFont(st::mentionFont->f);
p.setPen((selected ? st::mentionFgOverActive : st::mentionFgActive)->p);
p.drawText(mentionleft + namewidth + st::mentionPadding.right(), i * st::mentionHeight + st::mentionTop + st::mentionFont->ascent, first);
if (!second.isEmpty()) {
p.setPen((selected ? st::mentionFgOver : st::mentionFg)->p);
p.drawText(mentionleft + namewidth + st::mentionPadding.right() + firstwidth, i * st::mentionHeight + st::mentionTop + st::mentionFont->ascent, second);
}
} else if (!_hrows->isEmpty()) {
QString hrow = _hrows->at(i);
QString first = (_parent->filter().size() < 2) ? QString() : ('#' + hrow.mid(0, _parent->filter().size() - 1)), second = (_parent->filter().size() < 2) ? ('#' + hrow) : hrow.mid(_parent->filter().size() - 1);
int32 firstwidth = st::mentionFont->width(first), secondwidth = st::mentionFont->width(second);
if (htagwidth < firstwidth + secondwidth) {
if (htagwidth < firstwidth + st::mentionFont->elidew) {
first = st::mentionFont->elided(first + second, htagwidth);
second = QString();
} else {
second = st::mentionFont->elided(second, htagwidth - firstwidth);
}
}
p.setFont(st::mentionFont->f);
if (!first.isEmpty()) {
p.setPen((selected ? st::mentionFgOverActive : st::mentionFgActive)->p);
p.drawText(htagleft, i * st::mentionHeight + st::mentionTop + st::mentionFont->ascent, first);
}
if (!second.isEmpty()) {
p.setPen((selected ? st::mentionFgOver : st::mentionFg)->p);
p.drawText(htagleft + firstwidth, i * st::mentionHeight + st::mentionTop + st::mentionFont->ascent, second);
}
} else {
UserData *user = _brows->at(i).first;
const BotCommand *command = _brows->at(i).second;
QString toHighlight = command->command;
int32 botStatus = _parent->chat() ? _parent->chat()->botStatus : ((_parent->channel() && _parent->channel()->isMegagroup()) ? _parent->channel()->mgInfo->botStatus : -1);
if (hasUsername || botStatus == 0 || botStatus == 2) {
toHighlight += '@' + user->username;
}
user->loadUserpic();
user->paintUserpicLeft(p, st::mentionPhotoSize, st::mentionPadding.left(), i * st::mentionHeight + st::mentionPadding.top(), width());
int32 addleft = 0, widthleft = mentionwidth;
QString first = (_parent->filter().size() < 2) ? QString() : ('/' + toHighlight.mid(0, _parent->filter().size() - 1)), second = (_parent->filter().size() < 2) ? ('/' + toHighlight) : toHighlight.mid(_parent->filter().size() - 1);
int32 firstwidth = st::mentionFont->width(first), secondwidth = st::mentionFont->width(second);
if (widthleft < firstwidth + secondwidth) {
if (widthleft < firstwidth + st::mentionFont->elidew) {
first = st::mentionFont->elided(first + second, widthleft);
second = QString();
} else {
second = st::mentionFont->elided(second, widthleft - firstwidth);
}
}
p.setFont(st::mentionFont->f);
if (!first.isEmpty()) {
p.setPen((selected ? st::mentionFgOverActive : st::mentionFgActive)->p);
p.drawText(mentionleft, i * st::mentionHeight + st::mentionTop + st::mentionFont->ascent, first);
}
if (!second.isEmpty()) {
p.setPen((selected ? st::mentionFgOver : st::mentionFg)->p);
p.drawText(mentionleft + firstwidth, i * st::mentionHeight + st::mentionTop + st::mentionFont->ascent, second);
}
addleft += firstwidth + secondwidth + st::mentionPadding.left();
widthleft -= firstwidth + secondwidth + st::mentionPadding.left();
if (widthleft > st::mentionFont->elidew && !command->descriptionText().isEmpty()) {
p.setPen((selected ? st::mentionFgOver : st::mentionFg)->p);
command->descriptionText().drawElided(p, mentionleft + addleft, i * st::mentionHeight + st::mentionTop, widthleft, 1, style::al_right);
}
}
}
p.fillRect(Adaptive::OneColumn() ? 0 : st::lineWidth, _parent->innerBottom() - st::lineWidth, width() - (Adaptive::OneColumn() ? 0 : st::lineWidth), st::lineWidth, st::shadowColor->b);
}
p.fillRect(Adaptive::OneColumn() ? 0 : st::lineWidth, _parent->innerTop(), width() - (Adaptive::OneColumn() ? 0 : st::lineWidth), st::lineWidth, st::shadowColor->b);
}
void MentionsInner::resizeEvent(QResizeEvent *e) {
_stickersPerRow = qMax(1, int32(width() - 2 * st::stickerPanPadding) / int32(st::stickerPanSize.width()));
}
void MentionsInner::mouseMoveEvent(QMouseEvent *e) {
_mousePos = mapToGlobal(e->pos());
_mouseSel = true;
onUpdateSelected(true);
}
void MentionsInner::clearSel(bool hidden) {
_mouseSel = _overDelete = false;
setSel((_mrows->isEmpty() && _brows->isEmpty() && _hrows->isEmpty()) ? -1 : 0);
if (hidden) {
_down = -1;
_previewShown = false;
}
}
bool MentionsInner::moveSel(int key) {
_mouseSel = false;
int32 maxSel = (_mrows->isEmpty() ? (_hrows->isEmpty() ? (_brows->isEmpty() ? _srows->size() : _brows->size()) : _hrows->size()) : _mrows->size());
int32 direction = (key == Qt::Key_Up) ? -1 : (key == Qt::Key_Down ? 1 : 0);
if (!_srows->isEmpty()) {
if (key == Qt::Key_Left) {
direction = -1;
} else if (key == Qt::Key_Right) {
direction = 1;
} else {
direction *= _stickersPerRow;
}
}
if (_sel >= maxSel || _sel < 0) {
if (direction < -1) {
setSel(((maxSel - 1) / _stickersPerRow) * _stickersPerRow, true);
} else if (direction < 0) {
setSel(maxSel - 1, true);
} else {
setSel(0, true);
}
return (_sel >= 0 && _sel < maxSel);
}
setSel((_sel + direction >= maxSel || _sel + direction < 0) ? -1 : (_sel + direction), true);
return true;
}
bool MentionsInner::select() {
if (!_srows->isEmpty()) {
if (_sel >= 0 && _sel < _srows->size()) {
emit selected(_srows->at(_sel));
return true;
}
} else {
QString sel = getSelected();
if (!sel.isEmpty()) {
emit chosen(sel);
return true;
}
}
return false;
}
void MentionsInner::setRecentInlineBotsInRows(int32 bots) {
_recentInlineBotsInRows = bots;
}
QString MentionsInner::getSelected() const {
int32 maxSel = (_mrows->isEmpty() ? (_hrows->isEmpty() ? _brows->size() : _hrows->size()) : _mrows->size());
if (_sel >= 0 && _sel < maxSel) {
QString result;
if (!_mrows->isEmpty()) {
result = '@' + _mrows->at(_sel)->username;
} else if (!_hrows->isEmpty()) {
result = '#' + _hrows->at(_sel);
} else {
UserData *user = _brows->at(_sel).first;
const BotCommand *command(_brows->at(_sel).second);
int32 botStatus = _parent->chat() ? _parent->chat()->botStatus : ((_parent->channel() && _parent->channel()->isMegagroup()) ? _parent->channel()->mgInfo->botStatus : -1);
if (botStatus == 0 || botStatus == 2 || _parent->filter().indexOf('@') > 1) {
result = '/' + command->command + '@' + user->username;
} else {
result = '/' + command->command;
}
}
return result;
}
return QString();
}
void MentionsInner::mousePressEvent(QMouseEvent *e) {
_mousePos = mapToGlobal(e->pos());
_mouseSel = true;
onUpdateSelected(true);
if (e->button() == Qt::LeftButton) {
if (_overDelete && _sel >= 0 && _sel < (_mrows->isEmpty() ? _hrows->size() : _recentInlineBotsInRows)) {
_mousePos = mapToGlobal(e->pos());
bool removed = false;
if (_mrows->isEmpty()) {
QString toRemove = _hrows->at(_sel);
RecentHashtagPack &recent(cRefRecentWriteHashtags());
for (RecentHashtagPack::iterator i = recent.begin(); i != recent.cend();) {
if (i->first == toRemove) {
i = recent.erase(i);
removed = true;
} else {
++i;
}
}
} else {
UserData *toRemove = _mrows->at(_sel);
RecentInlineBots &recent(cRefRecentInlineBots());
int32 index = recent.indexOf(toRemove);
if (index >= 0) {
recent.remove(index);
removed = true;
}
}
if (removed) {
Local::writeRecentHashtagsAndBots();
}
_parent->updateFiltered();
_mouseSel = true;
onUpdateSelected(true);
} else if (_srows->isEmpty()) {
select();
} else {
_down = _sel;
_previewTimer.start(QApplication::startDragTime());
}
}
}
void MentionsInner::mouseReleaseEvent(QMouseEvent *e) {
_previewTimer.stop();
int32 pressed = _down;
_down = -1;
_mousePos = mapToGlobal(e->pos());
_mouseSel = true;
onUpdateSelected(true);
if (_previewShown) {
_previewShown = false;
return;
}
if (_sel < 0 || _sel != pressed || _srows->isEmpty()) return;
select();
}
void MentionsInner::enterEvent(QEvent *e) {
setMouseTracking(true);
_mousePos = QCursor::pos();
onUpdateSelected(true);
}
void MentionsInner::leaveEvent(QEvent *e) {
setMouseTracking(false);
if (_sel >= 0) {
setSel(-1);
}
}
void MentionsInner::updateSelectedRow() {
if (_sel >= 0) {
if (_srows->isEmpty()) {
update(0, _sel * st::mentionHeight, width(), st::mentionHeight);
} else {
int32 row = _sel / _stickersPerRow, col = _sel % _stickersPerRow;
update(st::stickerPanPadding + col * st::stickerPanSize.width(), st::stickerPanPadding + row * st::stickerPanSize.height(), st::stickerPanSize.width(), st::stickerPanSize.height());
}
}
}
void MentionsInner::setSel(int sel, bool scroll) {
updateSelectedRow();
_sel = sel;
updateSelectedRow();
if (scroll && _sel >= 0) {
if (_srows->isEmpty()) {
emit mustScrollTo(_sel * st::mentionHeight, (_sel + 1) * st::mentionHeight);
} else {
int32 row = _sel / _stickersPerRow;
emit mustScrollTo(st::stickerPanPadding + row * st::stickerPanSize.height(), st::stickerPanPadding + (row + 1) * st::stickerPanSize.height());
}
}
}
void MentionsInner::onUpdateSelected(bool force) {
QPoint mouse(mapFromGlobal(_mousePos));
if ((!force && !rect().contains(mouse)) || !_mouseSel) return;
if (_down >= 0 && !_previewShown) return;
int32 sel = -1, maxSel = 0;
if (!_srows->isEmpty()) {
int32 rows = rowscount(_srows->size(), _stickersPerRow);
int32 row = (mouse.y() >= st::stickerPanPadding) ? ((mouse.y() - st::stickerPanPadding) / st::stickerPanSize.height()) : -1;
int32 col = (mouse.x() >= st::stickerPanPadding) ? ((mouse.x() - st::stickerPanPadding) / st::stickerPanSize.width()) : -1;
if (row >= 0 && col >= 0) {
sel = row * _stickersPerRow + col;
}
maxSel = _srows->size();
_overDelete = false;
} else {
sel = mouse.y() / int32(st::mentionHeight);
maxSel = _mrows->isEmpty() ? (_hrows->isEmpty() ? _brows->size() : _hrows->size()) : _mrows->size();
_overDelete = (!_hrows->isEmpty() || (!_mrows->isEmpty() && sel < _recentInlineBotsInRows)) ? (mouse.x() >= width() - st::mentionHeight) : false;
}
if (sel < 0 || sel >= maxSel) {
sel = -1;
}
if (sel != _sel) {
setSel(sel);
if (_down >= 0 && _sel >= 0 && _down != _sel) {
_down = _sel;
if (_down >= 0 && _down < _srows->size()) {
Ui::showMediaPreview(_srows->at(_down));
}
}
}
}
void MentionsInner::onParentGeometryChanged() {
_mousePos = QCursor::pos();
if (rect().contains(mapFromGlobal(_mousePos))) {
setMouseTracking(true);
onUpdateSelected(true);
}
}
void MentionsInner::onPreview() {
if (_down >= 0 && _down < _srows->size()) {
Ui::showMediaPreview(_srows->at(_down));
_previewShown = true;
}
}
MentionsDropdown::MentionsDropdown(QWidget *parent) : TWidget(parent)
, _scroll(this, st::mentionScroll)
, _inner(this, &_mrows, &_hrows, &_brows, &_srows)
, _chat(0)
, _user(0)
, _channel(0)
, _hiding(false)
, a_opacity(0)
, _a_appearance(animation(this, &MentionsDropdown::step_appearance))
, _shadow(st::dropdownDef.shadow) {
_hideTimer.setSingleShot(true);
connect(&_hideTimer, SIGNAL(timeout()), this, SLOT(hideStart()));
connect(&_inner, SIGNAL(chosen(QString)), this, SIGNAL(chosen(QString)));
connect(&_inner, SIGNAL(selected(DocumentData*)), this, SIGNAL(stickerSelected(DocumentData*)));
connect(&_inner, SIGNAL(mustScrollTo(int,int)), &_scroll, SLOT(scrollToY(int,int)));
connect(App::wnd(), SIGNAL(imageLoaded()), &_inner, SLOT(update()));
setFocusPolicy(Qt::NoFocus);
_scroll.setFocusPolicy(Qt::NoFocus);
_scroll.viewport()->setFocusPolicy(Qt::NoFocus);
_inner.setGeometry(rect());
_scroll.setGeometry(rect());
_scroll.setWidget(&_inner);
_scroll.show();
_inner.show();
connect(&_scroll, SIGNAL(geometryChanged()), &_inner, SLOT(onParentGeometryChanged()));
connect(&_scroll, SIGNAL(scrolled()), &_inner, SLOT(onUpdateSelected()));
}
void MentionsDropdown::paintEvent(QPaintEvent *e) {
Painter p(this);
if (_a_appearance.animating()) {
p.setOpacity(a_opacity.current());
p.drawPixmap(0, 0, _cache);
return;
}
p.fillRect(rect(), st::white);
}
void MentionsDropdown::showFiltered(PeerData *peer, QString query, bool start) {
_chat = peer->asChat();
_user = peer->asUser();
_channel = peer->asChannel();
if (query.isEmpty()) {
rowsUpdated(MentionRows(), HashtagRows(), BotCommandRows(), _srows, false);
return;
}
_emoji = EmojiPtr();
query = query.toLower();
bool resetScroll = (_filter != query);
if (resetScroll) {
_filter = query;
}
_addInlineBots = start;
updateFiltered(resetScroll);
}
void MentionsDropdown::showStickers(EmojiPtr emoji) {
bool resetScroll = (_emoji != emoji);
_emoji = emoji;
if (!emoji) {
rowsUpdated(_mrows, _hrows, _brows, StickerPack(), false);
return;
}
_chat = 0;
_user = 0;
_channel = 0;
updateFiltered(resetScroll);
}
bool MentionsDropdown::clearFilteredBotCommands() {
if (_brows.isEmpty()) return false;
_brows.clear();
return true;
}
namespace {
template <typename T, typename U>
inline int indexOfInFirstN(const T &v, const U &elem, int last) {
for (auto b = v.cbegin(), i = b, e = b + qMax(v.size(), last); i != e; ++i) {
if (*i == elem) {
return (i - b);
}
}
return -1;
}
}
void MentionsDropdown::updateFiltered(bool resetScroll) {
int32 now = unixtime(), recentInlineBots = 0;
MentionRows mrows;
HashtagRows hrows;
BotCommandRows brows;
StickerPack srows;
if (_emoji) {
QMap<uint64, uint64> setsToRequest;
Stickers::Sets &sets(Global::RefStickerSets());
const Stickers::Order &order(Global::StickerSetsOrder());
for (int i = 0, l = order.size(); i < l; ++i) {
auto it = sets.find(order.at(i));
if (it != sets.cend()) {
if (it->emoji.isEmpty()) {
setsToRequest.insert(it->id, it->access);
it->flags |= MTPDstickerSet_ClientFlag::f_not_loaded;
} else if (!(it->flags & MTPDstickerSet::Flag::f_disabled)) {
StickersByEmojiMap::const_iterator i = it->emoji.constFind(emojiGetNoColor(_emoji));
if (i != it->emoji.cend()) {
srows += *i;
}
}
}
}
if (!setsToRequest.isEmpty() && App::api()) {
for (QMap<uint64, uint64>::const_iterator i = setsToRequest.cbegin(), e = setsToRequest.cend(); i != e; ++i) {
App::api()->scheduleStickerSetRequest(i.key(), i.value());
}
App::api()->requestStickerSets();
}
} else if (_filter.at(0) == '@') {
if (_chat) {
mrows.reserve((_addInlineBots ? cRecentInlineBots().size() : 0) + (_chat->participants.isEmpty() ? _chat->lastAuthors.size() : _chat->participants.size()));
} else if (_channel && _channel->isMegagroup()) {
if (_channel->mgInfo->lastParticipants.isEmpty() || _channel->lastParticipantsCountOutdated()) {
} else {
mrows.reserve((_addInlineBots ? cRecentInlineBots().size() : 0) + _channel->mgInfo->lastParticipants.size());
}
} else if (_addInlineBots) {
mrows.reserve(cRecentInlineBots().size());
}
if (_addInlineBots) {
for (RecentInlineBots::const_iterator i = cRecentInlineBots().cbegin(), e = cRecentInlineBots().cend(); i != e; ++i) {
UserData *user = *i;
if (user->username.isEmpty()) continue;
if (_filter.size() > 1 && (!user->username.startsWith(_filter.midRef(1), Qt::CaseInsensitive) || user->username.size() + 1 == _filter.size())) continue;
mrows.push_back(user);
++recentInlineBots;
}
}
if (_chat) {
QMultiMap<int32, UserData*> ordered;
mrows.reserve(mrows.size() + (_chat->participants.isEmpty() ? _chat->lastAuthors.size() : _chat->participants.size()));
if (_chat->noParticipantInfo()) {
if (App::api()) App::api()->requestFullPeer(_chat);
} else if (!_chat->participants.isEmpty()) {
for (ChatData::Participants::const_iterator i = _chat->participants.cbegin(), e = _chat->participants.cend(); i != e; ++i) {
UserData *user = i.key();
if (user->username.isEmpty()) continue;
if (_filter.size() > 1 && (!user->username.startsWith(_filter.midRef(1), Qt::CaseInsensitive) || user->username.size() + 1 == _filter.size())) continue;
if (indexOfInFirstN(mrows, user, recentInlineBots) >= 0) continue;
ordered.insertMulti(App::onlineForSort(user, now), user);
}
}
for (MentionRows::const_iterator i = _chat->lastAuthors.cbegin(), e = _chat->lastAuthors.cend(); i != e; ++i) {
UserData *user = *i;
if (user->username.isEmpty()) continue;
if (_filter.size() > 1 && (!user->username.startsWith(_filter.midRef(1), Qt::CaseInsensitive) || user->username.size() + 1 == _filter.size())) continue;
if (indexOfInFirstN(mrows, user, recentInlineBots) >= 0) continue;
mrows.push_back(user);
if (!ordered.isEmpty()) {
ordered.remove(App::onlineForSort(user, now), user);
}
}
if (!ordered.isEmpty()) {
for (QMultiMap<int32, UserData*>::const_iterator i = ordered.cend(), b = ordered.cbegin(); i != b;) {
--i;
mrows.push_back(i.value());
}
}
} else if (_channel && _channel->isMegagroup()) {
QMultiMap<int32, UserData*> ordered;
if (_channel->mgInfo->lastParticipants.isEmpty() || _channel->lastParticipantsCountOutdated()) {
if (App::api()) App::api()->requestLastParticipants(_channel);
} else {
mrows.reserve(mrows.size() + _channel->mgInfo->lastParticipants.size());
for (MegagroupInfo::LastParticipants::const_iterator i = _channel->mgInfo->lastParticipants.cbegin(), e = _channel->mgInfo->lastParticipants.cend(); i != e; ++i) {
UserData *user = *i;
if (user->username.isEmpty()) continue;
if (_filter.size() > 1 && (!user->username.startsWith(_filter.midRef(1), Qt::CaseInsensitive) || user->username.size() + 1 == _filter.size())) continue;
if (indexOfInFirstN(mrows, user, recentInlineBots) >= 0) continue;
mrows.push_back(user);
}
}
}
} else if (_filter.at(0) == '#') {
const RecentHashtagPack &recent(cRecentWriteHashtags());
hrows.reserve(recent.size());
for (RecentHashtagPack::const_iterator i = recent.cbegin(), e = recent.cend(); i != e; ++i) {
if (_filter.size() > 1 && (!i->first.startsWith(_filter.midRef(1), Qt::CaseInsensitive) || i->first.size() + 1 == _filter.size())) continue;
hrows.push_back(i->first);
}
} else if (_filter.at(0) == '/') {
bool hasUsername = _filter.indexOf('@') > 1;
QMap<UserData*, bool> bots;
int32 cnt = 0;
if (_chat) {
if (_chat->noParticipantInfo()) {
if (App::api()) App::api()->requestFullPeer(_chat);
} else if (!_chat->participants.isEmpty()) {
for (ChatData::Participants::const_iterator i = _chat->participants.cbegin(), e = _chat->participants.cend(); i != e; ++i) {
UserData *user = i.key();
if (!user->botInfo) continue;
if (!user->botInfo->inited && App::api()) App::api()->requestFullPeer(user);
if (user->botInfo->commands.isEmpty()) continue;
bots.insert(user, true);
cnt += user->botInfo->commands.size();
}
}
} else if (_user && _user->botInfo) {
if (!_user->botInfo->inited && App::api()) App::api()->requestFullPeer(_user);
cnt = _user->botInfo->commands.size();
bots.insert(_user, true);
} else if (_channel && _channel->isMegagroup()) {
if (_channel->mgInfo->bots.isEmpty()) {
if (!_channel->mgInfo->botStatus && App::api()) App::api()->requestBots(_channel);
} else {
for_const (auto user, _channel->mgInfo->bots) {
if (!user->botInfo) continue;
if (!user->botInfo->inited && App::api()) App::api()->requestFullPeer(user);
if (user->botInfo->commands.isEmpty()) continue;
bots.insert(user, true);
cnt += user->botInfo->commands.size();
}
}
}
if (cnt) {
brows.reserve(cnt);
int32 botStatus = _chat ? _chat->botStatus : ((_channel && _channel->isMegagroup()) ? _channel->mgInfo->botStatus : -1);
if (_chat) {
for (MentionRows::const_iterator i = _chat->lastAuthors.cbegin(), e = _chat->lastAuthors.cend(); i != e; ++i) {
UserData *user = *i;
if (!user->botInfo) continue;
if (!bots.contains(user)) continue;
if (!user->botInfo->inited && App::api()) App::api()->requestFullPeer(user);
if (user->botInfo->commands.isEmpty()) continue;
bots.remove(user);
for (int32 j = 0, l = user->botInfo->commands.size(); j < l; ++j) {
if (_filter.size() > 1) {
QString toFilter = (hasUsername || botStatus == 0 || botStatus == 2) ? user->botInfo->commands.at(j).command + '@' + user->username : user->botInfo->commands.at(j).command;
if (!toFilter.startsWith(_filter.midRef(1), Qt::CaseInsensitive)/* || toFilter.size() + 1 == _filter.size()*/) continue;
}
brows.push_back(qMakePair(user, &user->botInfo->commands.at(j)));
}
}
}
if (!bots.isEmpty()) {
for (QMap<UserData*, bool>::const_iterator i = bots.cbegin(), e = bots.cend(); i != e; ++i) {
UserData *user = i.key();
for (int32 j = 0, l = user->botInfo->commands.size(); j < l; ++j) {
if (_filter.size() > 1) {
QString toFilter = (hasUsername || botStatus == 0 || botStatus == 2) ? user->botInfo->commands.at(j).command + '@' + user->username : user->botInfo->commands.at(j).command;
if (!toFilter.startsWith(_filter.midRef(1), Qt::CaseInsensitive)/* || toFilter.size() + 1 == _filter.size()*/) continue;
}
brows.push_back(qMakePair(user, &user->botInfo->commands.at(j)));
}
}
}
}
}
rowsUpdated(mrows, hrows, brows, srows, resetScroll);
_inner.setRecentInlineBotsInRows(recentInlineBots);
}
void MentionsDropdown::rowsUpdated(const MentionRows &mrows, const HashtagRows &hrows, const BotCommandRows &brows, const StickerPack &srows, bool resetScroll) {
if (mrows.isEmpty() && hrows.isEmpty() && brows.isEmpty() && srows.isEmpty()) {
if (!isHidden()) {
hideStart();
}
_mrows.clear();
_hrows.clear();
_brows.clear();
_srows.clear();
} else {
_mrows = mrows;
_hrows = hrows;
_brows = brows;
_srows = srows;
bool hidden = _hiding || isHidden();
if (hidden) {
show();
_scroll.show();
}
recount(resetScroll);
update();
if (hidden) {
hide();
showStart();
}
}
}
void MentionsDropdown::setBoundings(QRect boundings) {
_boundings = boundings;
recount();
}
void MentionsDropdown::recount(bool resetScroll) {
int32 h = 0, oldst = _scroll.scrollTop(), st = oldst, maxh = 4.5 * st::mentionHeight;
if (!_srows.isEmpty()) {
int32 stickersPerRow = qMax(1, int32(_boundings.width() - 2 * st::stickerPanPadding) / int32(st::stickerPanSize.width()));
int32 rows = rowscount(_srows.size(), stickersPerRow);
h = st::stickerPanPadding + rows * st::stickerPanSize.height();
} else if (!_mrows.isEmpty()) {
h = _mrows.size() * st::mentionHeight;
} else if (!_hrows.isEmpty()) {
h = _hrows.size() * st::mentionHeight;
} else if (!_brows.isEmpty()) {
h = _brows.size() * st::mentionHeight;
}
if (_inner.width() != _boundings.width() || _inner.height() != h) {
_inner.resize(_boundings.width(), h);
}
if (h > _boundings.height()) h = _boundings.height();
if (h > maxh) h = maxh;
if (width() != _boundings.width() || height() != h) {
setGeometry(_boundings.x(), _boundings.y() + _boundings.height() - h, _boundings.width(), h);
_scroll.resize(_boundings.width(), h);
} else if (y() != _boundings.y() + _boundings.height() - h) {
move(_boundings.x(), _boundings.y() + _boundings.height() - h);
}
if (resetScroll) st = 0;
if (st != oldst) _scroll.scrollToY(st);
if (resetScroll) _inner.clearSel();
}
void MentionsDropdown::fastHide() {
if (_a_appearance.animating()) {
_a_appearance.stop();
}
a_opacity = anim::fvalue(0, 0);
_hideTimer.stop();
hideFinish();
}
void MentionsDropdown::hideStart() {
if (!_hiding) {
if (_cache.isNull()) {
_scroll.show();
_cache = myGrab(this);
}
_scroll.hide();
_hiding = true;
a_opacity.start(0);
setAttribute(Qt::WA_OpaquePaintEvent, false);
_a_appearance.start();
}
}
void MentionsDropdown::hideFinish() {
hide();
_hiding = false;
_filter = qsl("-");
_inner.clearSel(true);
}
void MentionsDropdown::showStart() {
if (!isHidden() && a_opacity.current() == 1 && !_hiding) {
return;
}
if (_cache.isNull()) {
_scroll.show();
_cache = myGrab(this);
}
_scroll.hide();
_hiding = false;
show();
a_opacity.start(1);
setAttribute(Qt::WA_OpaquePaintEvent, false);
_a_appearance.start();
}
void MentionsDropdown::step_appearance(float64 ms, bool timer) {
float64 dt = ms / st::dropdownDef.duration;
if (dt >= 1) {
_a_appearance.stop();
a_opacity.finish();
_cache = QPixmap();
setAttribute(Qt::WA_OpaquePaintEvent);
if (_hiding) {
hideFinish();
} else {
_scroll.show();
_inner.clearSel();
}
} else {
a_opacity.update(dt, anim::linear);
}
if (timer) update();
}
const QString &MentionsDropdown::filter() const {
return _filter;
}
ChatData *MentionsDropdown::chat() const {
return _chat;
}
ChannelData *MentionsDropdown::channel() const {
return _channel;
}
UserData *MentionsDropdown::user() const {
return _user;
}
int32 MentionsDropdown::innerTop() {
return _scroll.scrollTop();
}
int32 MentionsDropdown::innerBottom() {
return _scroll.scrollTop() + _scroll.height();
}
QString MentionsDropdown::getSelected() const {
return _inner.getSelected();
}
bool MentionsDropdown::eventFilter(QObject *obj, QEvent *e) {
if (isHidden()) return QWidget::eventFilter(obj, e);
if (e->type() == QEvent::KeyPress) {
QKeyEvent *ev = static_cast<QKeyEvent*>(e);
if (!(ev->modifiers() & (Qt::AltModifier | Qt::ControlModifier | Qt::ShiftModifier | Qt::MetaModifier))) {
if (ev->key() == Qt::Key_Up || ev->key() == Qt::Key_Down || (!_srows.isEmpty() && (ev->key() == Qt::Key_Left || ev->key() == Qt::Key_Right))) {
return _inner.moveSel(ev->key());
} else if (ev->key() == Qt::Key_Enter || ev->key() == Qt::Key_Return) {
return _inner.select();
}
}
}
return QWidget::eventFilter(obj, e);
}
MentionsDropdown::~MentionsDropdown() {
}

View file

@ -740,156 +740,3 @@ private:
bool inlineResultsFail(const RPCError &error);
};
typedef QList<UserData*> MentionRows;
typedef QList<QString> HashtagRows;
typedef QList<QPair<UserData*, const BotCommand*> > BotCommandRows;
class MentionsDropdown;
class MentionsInner : public TWidget {
Q_OBJECT
public:
MentionsInner(MentionsDropdown *parent, MentionRows *mrows, HashtagRows *hrows, BotCommandRows *brows, StickerPack *srows);
void paintEvent(QPaintEvent *e);
void resizeEvent(QResizeEvent *e);
void enterEvent(QEvent *e);
void leaveEvent(QEvent *e);
void mousePressEvent(QMouseEvent *e);
void mouseMoveEvent(QMouseEvent *e);
void mouseReleaseEvent(QMouseEvent *e);
void clearSel(bool hidden = false);
bool moveSel(int key);
bool select();
void setRecentInlineBotsInRows(int32 bots);
QString getSelected() const;
signals:
void chosen(QString mentionOrHashtag);
void selected(DocumentData *sticker);
void mustScrollTo(int scrollToTop, int scrollToBottom);
public slots:
void onParentGeometryChanged();
void onUpdateSelected(bool force = false);
void onPreview();
private:
void updateSelectedRow();
void setSel(int sel, bool scroll = false);
MentionsDropdown *_parent;
MentionRows *_mrows;
HashtagRows *_hrows;
BotCommandRows *_brows;
StickerPack *_srows;
int32 _stickersPerRow, _recentInlineBotsInRows;
int32 _sel, _down;
bool _mouseSel;
QPoint _mousePos;
bool _overDelete;
bool _previewShown;
QTimer _previewTimer;
};
class MentionsDropdown : public TWidget {
Q_OBJECT
public:
MentionsDropdown(QWidget *parent);
void paintEvent(QPaintEvent *e);
void fastHide();
bool clearFilteredBotCommands();
void showFiltered(PeerData *peer, QString query, bool start);
void showStickers(EmojiPtr emoji);
void updateFiltered(bool resetScroll = false);
void setBoundings(QRect boundings);
void step_appearance(float64 ms, bool timer);
const QString &filter() const;
ChatData *chat() const;
ChannelData *channel() const;
UserData *user() const;
int32 innerTop();
int32 innerBottom();
bool eventFilter(QObject *obj, QEvent *e);
QString getSelected() const;
bool stickersShown() const {
return !_srows.isEmpty();
}
bool overlaps(const QRect &globalRect) {
if (isHidden() || !testAttribute(Qt::WA_OpaquePaintEvent)) return false;
return rect().contains(QRect(mapFromGlobal(globalRect.topLeft()), globalRect.size()));
}
~MentionsDropdown();
signals:
void chosen(QString mentionOrHashtag);
void stickerSelected(DocumentData *sticker);
public slots:
void hideStart();
void hideFinish();
void showStart();
private:
void recount(bool resetScroll = false);
QPixmap _cache;
MentionRows _mrows;
HashtagRows _hrows;
BotCommandRows _brows;
StickerPack _srows;
void rowsUpdated(const MentionRows &mrows, const HashtagRows &hrows, const BotCommandRows &brows, const StickerPack &srows, bool resetScroll);
ScrollArea _scroll;
MentionsInner _inner;
ChatData *_chat;
UserData *_user;
ChannelData *_channel;
EmojiPtr _emoji;
QString _filter;
QRect _boundings;
bool _addInlineBots;
int32 _width, _height;
bool _hiding;
anim::fvalue a_opacity;
Animation _a_appearance;
QTimer _hideTimer;
BoxShadow _shadow;
};

View file

@ -236,6 +236,10 @@ void autoplayMediaInlineAsync(const FullMsgId &msgId) {
}
}
void showPeerProfile(const PeerId &peer) {
if (MainWidget *m = App::main()) m->showPeerProfile(App::peer(peer));
}
void showPeerHistory(const PeerId &peer, MsgId msgId, bool back) {
if (MainWidget *m = App::main()) m->ui_showPeerHistory(peer, msgId, back);
}

View file

@ -67,6 +67,14 @@ void repaintInlineItem(const InlineBots::Layout::ItemBase *layout);
bool isInlineItemVisible(const InlineBots::Layout::ItemBase *reader);
void autoplayMediaInlineAsync(const FullMsgId &msgId);
void showPeerProfile(const PeerId &peer);
inline void showPeerProfile(const PeerData *peer) {
showPeerProfile(peer->id);
}
inline void showPeerProfile(const History *history) {
showPeerProfile(history->peer->id);
}
void showPeerHistory(const PeerId &peer, MsgId msgId, bool back = false);
inline void showPeerHistory(const PeerData *peer, MsgId msgId, bool back = false) {
showPeerHistory(peer->id, msgId, back);

View file

@ -1029,9 +1029,10 @@ HistoryItem *History::createItem(const MTPMessage &msg, bool applyServiceAction,
}
if (badMedia == 1) {
QString text(lng_message_unsupported(lt_link, qsl("https://desktop.telegram.org")));
EntitiesInText entities = textParseEntities(text, _historyTextNoMonoOptions.flags);
EntitiesInText entities;
textParseEntities(text, _historyTextNoMonoOptions.flags, &entities);
entities.push_front(EntityInText(EntityInTextItalic, 0, text.size()));
result = HistoryMessage::create(this, m.vid.v, m.vflags.v, m.vreply_to_msg_id.v, m.vvia_bot_id.v, date(m.vdate), m.vfrom_id.v, text, entities);
result = HistoryMessage::create(this, m.vid.v, m.vflags.v, m.vreply_to_msg_id.v, m.vvia_bot_id.v, date(m.vdate), m.vfrom_id.v, { text, entities });
} else if (badMedia) {
result = HistoryService::create(this, m.vid.v, date(m.vdate), lang(lng_message_empty), m.vflags.v, m.has_from_id() ? m.vfrom_id.v : 0);
} else {
@ -3094,26 +3095,25 @@ void HistoryItem::setId(MsgId newId) {
}
bool HistoryItem::canEdit(const QDateTime &cur) const {
int32 s = date.secsTo(cur);
auto channel = _history->peer->asChannel();
if (!channel || id < 0 || date.secsTo(cur) >= Global::EditTimeLimit()) return false;
if (id < 0 || date.secsTo(cur) >= Global::EditTimeLimit()) return false;
if (const HistoryMessage *msg = toHistoryMessage()) {
if (auto msg = toHistoryMessage()) {
if (msg->Has<HistoryMessageVia>() || msg->Has<HistoryMessageForwarded>()) return false;
if (HistoryMedia *media = msg->getMedia()) {
HistoryMediaType t = media->type();
if (t != MediaTypePhoto &&
t != MediaTypeVideo &&
t != MediaTypeFile &&
t != MediaTypeGif &&
t != MediaTypeMusicFile &&
t != MediaTypeVoiceFile &&
t != MediaTypeWebPage) {
auto type = media->type();
if (type != MediaTypePhoto &&
type != MediaTypeVideo &&
type != MediaTypeFile &&
type != MediaTypeGif &&
type != MediaTypeMusicFile &&
type != MediaTypeVoiceFile &&
type != MediaTypeWebPage) {
return false;
}
}
if (isPost()) {
auto channel = _history->peer->asChannel();
return (channel->amCreator() || (channel->amEditor() && out()));
}
return out();
@ -3303,18 +3303,24 @@ int32 gifMaxStatusWidth(DocumentData *document) {
return result;
}
QString captionedSelectedText(const QString &attachType, const Text &caption, TextSelection selection) {
TextWithEntities captionedSelectedText(const QString &attachType, const Text &caption, TextSelection selection) {
if (selection != FullSelection) {
return caption.original(selection, Text::ExpandLinksAll);
return caption.originalTextWithEntities(selection, ExpandLinksAll);
}
QString result;
result.reserve(5 + attachType.size() + caption.length());
result.append(qstr("[ ")).append(attachType).append(qstr(" ]"));
TextWithEntities result, original;
if (!caption.isEmpty()) {
result.append(qstr("\n")).append(caption.original(AllTextSelection));
original = caption.originalTextWithEntities(AllTextSelection, ExpandLinksAll);
}
result.text.reserve(5 + attachType.size() + original.text.size());
result.text.append(qstr("[ ")).append(attachType).append(qstr(" ]"));
if (!caption.isEmpty()) {
result.text.append(qstr("\n"));
appendTextWithEntities(result, std_::move(original));
}
return result;
}
} // namespace
void HistoryFileMedia::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) {
@ -3734,10 +3740,10 @@ void HistoryPhoto::detachFromParent() {
}
QString HistoryPhoto::inDialogsText() const {
return _caption.isEmpty() ? lang(lng_in_dlg_photo) : _caption.original(AllTextSelection, Text::ExpandLinksNone);
return _caption.isEmpty() ? lang(lng_in_dlg_photo) : _caption.originalText(AllTextSelection, ExpandLinksNone);
}
QString HistoryPhoto::selectedText(TextSelection selection) const {
TextWithEntities HistoryPhoto::selectedText(TextSelection selection) const {
return captionedSelectedText(lang(lng_in_dlg_photo), _caption, selection);
}
@ -3980,10 +3986,10 @@ void HistoryVideo::setStatusSize(int32 newSize) const {
}
QString HistoryVideo::inDialogsText() const {
return _caption.isEmpty() ? lang(lng_in_dlg_video) : _caption.original(AllTextSelection, Text::ExpandLinksNone);
return _caption.isEmpty() ? lang(lng_in_dlg_video) : _caption.originalText(AllTextSelection, ExpandLinksNone);
}
QString HistoryVideo::selectedText(TextSelection selection) const {
TextWithEntities HistoryVideo::selectedText(TextSelection selection) const {
return captionedSelectedText(lang(lng_in_dlg_video), _caption, selection);
}
@ -4471,13 +4477,13 @@ QString HistoryDocument::inDialogsText() const {
}
if (auto captioned = Get<HistoryDocumentCaptioned>()) {
if (!captioned->_caption.isEmpty()) {
result.append(' ').append(captioned->_caption.original(AllTextSelection, Text::ExpandLinksNone));
result.append(' ').append(captioned->_caption.originalText(AllTextSelection, ExpandLinksNone));
}
}
return result;
}
QString HistoryDocument::selectedText(TextSelection selection) const {
TextWithEntities HistoryDocument::selectedText(TextSelection selection) const {
const Text emptyCaption;
const Text *caption = &emptyCaption;
if (auto captioned = Get<HistoryDocumentCaptioned>()) {
@ -4929,10 +4935,10 @@ HistoryTextState HistoryGif::getState(int x, int y, HistoryStateRequest request)
}
QString HistoryGif::inDialogsText() const {
return qsl("GIF") + (_caption.isEmpty() ? QString() : (' ' + _caption.original(AllTextSelection, Text::ExpandLinksNone)));
return qsl("GIF") + (_caption.isEmpty() ? QString() : (' ' + _caption.originalText(AllTextSelection, ExpandLinksNone)));
}
QString HistoryGif::selectedText(TextSelection selection) const {
TextWithEntities HistoryGif::selectedText(TextSelection selection) const {
return captionedSelectedText(qsl("GIF"), _caption, selection);
}
@ -5249,11 +5255,11 @@ QString HistorySticker::inDialogsText() const {
return _emoji.isEmpty() ? lang(lng_in_dlg_sticker) : lng_in_dlg_sticker_emoji(lt_emoji, _emoji);
}
QString HistorySticker::selectedText(TextSelection selection) const {
TextWithEntities HistorySticker::selectedText(TextSelection selection) const {
if (selection != FullSelection) {
return QString();
return TextWithEntities();
}
return qsl("[ ") + inDialogsText() + qsl(" ]");
return { qsl("[ ") + inDialogsText() + qsl(" ]"), EntitiesInText() };
}
void HistorySticker::attachToParent() {
@ -5350,6 +5356,9 @@ void HistoryContact::initDimensions() {
if (_userId) {
_minh = st::msgFileThumbPadding.top() + st::msgFileThumbSize + st::msgFileThumbPadding.bottom();
if (_parent->Has<HistoryMessageSigned>()) {
_minh += st::msgDateFont->height - st::msgDateDelta.y();
}
} else {
_minh = st::msgFilePadding.top() + st::msgFileSize + st::msgFilePadding.bottom();
}
@ -5434,11 +5443,11 @@ QString HistoryContact::inDialogsText() const {
return lang(lng_in_dlg_contact);
}
QString HistoryContact::selectedText(TextSelection selection) const {
TextWithEntities HistoryContact::selectedText(TextSelection selection) const {
if (selection != FullSelection) {
return QString();
return TextWithEntities();
}
return qsl("[ ") + lang(lng_in_dlg_contact) + qsl(" ]\n") + _name.original() + '\n' + _phone;
return { qsl("[ ") + lang(lng_in_dlg_contact) + qsl(" ]\n") + _name.originalText() + '\n' + _phone, EntitiesInText() };
}
void HistoryContact::attachToParent() {
@ -5955,18 +5964,21 @@ QString HistoryWebPage::inDialogsText() const {
return QString();
}
QString HistoryWebPage::selectedText(TextSelection selection) const {
TextWithEntities HistoryWebPage::selectedText(TextSelection selection) const {
if (selection == FullSelection) {
return QString();
return TextWithEntities();
}
auto titleResult = _title.original(selection, Text::ExpandLinksAll);
auto descriptionResult = _description.original(toDescriptionSelection(selection), Text::ExpandLinksAll);
if (titleResult.isEmpty()) {
auto titleResult = _title.originalTextWithEntities(selection, ExpandLinksAll);
auto descriptionResult = _description.originalTextWithEntities(toDescriptionSelection(selection), ExpandLinksAll);
if (titleResult.text.isEmpty()) {
return descriptionResult;
} else if (descriptionResult.isEmpty()) {
} else if (descriptionResult.text.isEmpty()) {
return titleResult;
}
return titleResult + '\n' + descriptionResult;
titleResult.text += '\n';
appendTextWithEntities(titleResult, std_::move(descriptionResult));
return titleResult;
}
ImagePtr HistoryWebPage::replyPreview() {
@ -6398,24 +6410,31 @@ TextSelection HistoryLocation::adjustSelection(TextSelection selection, TextSele
}
QString HistoryLocation::inDialogsText() const {
return _title.isEmpty() ? lang(lng_maps_point) : _title.original(AllTextSelection);
return _title.isEmpty() ? lang(lng_maps_point) : _title.originalText(AllTextSelection);
}
QString HistoryLocation::selectedText(TextSelection selection) const {
TextWithEntities HistoryLocation::selectedText(TextSelection selection) const {
if (selection == FullSelection) {
auto result = qsl("[ ") + lang(lng_maps_point) + qsl(" ]\n");
TextWithEntities result = { qsl("[ ") + lang(lng_maps_point) + qsl(" ]\n"), EntitiesInText() };
auto info = selectedText(AllTextSelection);
if (!info.isEmpty()) result.append(info).append('\n');
return result + _link->text();
if (!info.text.isEmpty()) {
appendTextWithEntities(result, std_::move(info));
result.text.append('\n');
}
auto titleResult = _title.original(selection);
auto descriptionResult = _description.original(toDescriptionSelection(selection));
if (titleResult.isEmpty()) {
result.text += _link->dragText();
return result;
}
auto titleResult = _title.originalTextWithEntities(selection);
auto descriptionResult = _description.originalTextWithEntities(toDescriptionSelection(selection));
if (titleResult.text.isEmpty()) {
return descriptionResult;
} else if (descriptionResult.isEmpty()) {
} else if (descriptionResult.text.isEmpty()) {
return titleResult;
}
return titleResult + '\n' + descriptionResult;
titleResult.text += '\n';
appendTextWithEntities(titleResult, std_::move(descriptionResult));
return titleResult;
}
int32 HistoryLocation::fullWidth() const {
@ -6468,7 +6487,7 @@ void HistoryMessageEdited::create(const QDateTime &editDate, const QDateTime &da
_editDate = editDate;
QString time = date.toString(cTimeFormat());
_edited.setText(st::msgDateFont, time, _textNameOptions);
_edited.setText(st::msgDateFont, lang(lng_edited) + ' ' + time, _textNameOptions);
}
int HistoryMessageEdited::maxWidth() const {
@ -6727,10 +6746,42 @@ HistoryMessage::HistoryMessage(History *history, const MTPDmessage &msg)
createComponents(config);
QString text(textClean(qs(msg.vmessage)));
initMedia(msg.has_media() ? (&msg.vmedia) : 0, text);
setText(text, msg.has_entities() ? entitiesFromMTP(msg.ventities.c_vector().v) : EntitiesInText());
initMedia(msg.has_media() ? (&msg.vmedia) : nullptr, text);
TextWithEntities textWithEntities = { text, EntitiesInText() };
if (msg.has_entities()) {
textWithEntities.entities = entitiesFromMTP(msg.ventities.c_vector().v);
}
setText(textWithEntities);
}
namespace {
MTPDmessage::Flags newForwardedFlags(PeerData *p, int32 from, HistoryMessage *fwd) {
MTPDmessage::Flags result = newMessageFlags(p) | MTPDmessage::Flag::f_fwd_from;
if (from) {
result |= MTPDmessage::Flag::f_from_id;
}
if (fwd->Has<HistoryMessageVia>()) {
result |= MTPDmessage::Flag::f_via_bot_id;
}
if (!p->isChannel()) {
if (HistoryMedia *media = fwd->getMedia()) {
if (media->type() == MediaTypeVoiceFile) {
result |= MTPDmessage::Flag::f_media_unread;
// } else if (media->type() == MediaTypeVideo) {
// result |= MTPDmessage::flag_media_unread;
}
}
}
if (fwd->hasViews()) {
result |= MTPDmessage::Flag::f_views;
}
return result;
}
} // namespace
HistoryMessage::HistoryMessage(History *history, MsgId id, MTPDmessage::Flags flags, QDateTime date, int32 from, HistoryMessage *fwd)
: HistoryItem(history, id, newForwardedFlags(history->peer, from, fwd) | flags, date, from) {
CreateConfig config;
@ -6754,14 +6805,14 @@ HistoryMessage::HistoryMessage(History *history, MsgId id, MTPDmessage::Flags fl
if (HistoryMedia *mediaOriginal = fwd->getMedia()) {
_media.reset(mediaOriginal->clone(this));
}
setText(fwd->originalText(), fwd->originalEntities());
setText(fwd->originalText());
}
HistoryMessage::HistoryMessage(History *history, MsgId id, MTPDmessage::Flags flags, MsgId replyTo, int32 viaBotId, QDateTime date, int32 from, const QString &msg, const EntitiesInText &entities)
HistoryMessage::HistoryMessage(History *history, MsgId id, MTPDmessage::Flags flags, MsgId replyTo, int32 viaBotId, QDateTime date, int32 from, const TextWithEntities &textWithEntities)
: HistoryItem(history, id, flags, date, (flags & MTPDmessage::Flag::f_from_id) ? from : 0) {
createComponentsHelper(flags, replyTo, viaBotId, MTPnullMarkup);
setText(msg, entities);
setText(textWithEntities);
}
HistoryMessage::HistoryMessage(History *history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, int32 viaBotId, QDateTime date, int32 from, DocumentData *doc, const QString &caption, const MTPReplyMarkup &markup)
@ -6769,7 +6820,7 @@ HistoryMessage::HistoryMessage(History *history, MsgId msgId, MTPDmessage::Flags
createComponentsHelper(flags, replyTo, viaBotId, markup);
initMediaFromDocument(doc, caption);
setText(QString(), EntitiesInText());
setText(TextWithEntities());
}
HistoryMessage::HistoryMessage(History *history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, int32 viaBotId, QDateTime date, int32 from, PhotoData *photo, const QString &caption, const MTPReplyMarkup &markup)
@ -6777,7 +6828,7 @@ HistoryMessage::HistoryMessage(History *history, MsgId msgId, MTPDmessage::Flags
createComponentsHelper(flags, replyTo, viaBotId, markup);
_media.reset(new HistoryPhoto(this, photo, caption));
setText(QString(), EntitiesInText());
setText(TextWithEntities());
}
void HistoryMessage::createComponentsHelper(MTPDmessage::Flags flags, MsgId replyTo, int32 viaBotId, const MTPReplyMarkup &markup) {
@ -6822,7 +6873,7 @@ void HistoryMessage::createComponents(const CreateConfig &config) {
if (auto reply = Get<HistoryMessageReply>()) {
reply->replyToMsgId = config.replyTo;
if (!reply->updateData(this) && App::api()) {
App::api()->requestMessageData(history()->peer->asChannel(), reply->replyToMsgId, new HistoryDependentItemCallback(fullId()));
App::api()->requestMessageData(history()->peer->asChannel(), reply->replyToMsgId, std_::make_unique<HistoryDependentItemCallback>(fullId()));
}
}
if (auto via = Get<HistoryMessageVia>()) {
@ -6959,12 +7010,12 @@ void HistoryMessage::initDimensions() {
if (_media->isDisplayed()) {
if (_text.hasSkipBlock()) {
_text.removeSkipBlock();
_textWidth = 0;
_textWidth = -1;
_textHeight = 0;
}
} else if (!_text.hasSkipBlock()) {
_text.setSkipBlock(skipBlockWidth(), skipBlockHeight());
_textWidth = 0;
_textWidth = -1;
_textHeight = 0;
}
}
@ -7068,11 +7119,6 @@ void HistoryMessage::applyEdition(const MTPDmessage &message) {
}
}
EntitiesInText entities;
if (message.has_entities()) {
entities = entitiesFromMTP(message.ventities.c_vector().v);
}
if (message.has_edit_date()) {
_flags |= MTPDmessage::Flag::f_edit_date;
if (!Has<HistoryMessageEdited>()) {
@ -7082,7 +7128,11 @@ void HistoryMessage::applyEdition(const MTPDmessage &message) {
initTime();
}
setText(qs(message.vmessage), entities);
TextWithEntities textWithEntities = { qs(message.vmessage), EntitiesInText() };
if (message.has_entities()) {
textWithEntities.entities = entitiesFromMTP(message.ventities.c_vector().v);
}
setText(textWithEntities);
setMedia(message.has_media() ? (&message.vmedia) : nullptr);
setReplyMarkup(message.has_reply_markup() ? (&message.vreply_markup) : nullptr);
setViewsCount(message.has_views() ? message.vviews.v : -1);
@ -7102,6 +7152,8 @@ void HistoryMessage::applyEdition(const MTPDmessage &message) {
keyboard->oldTop = keyboardTop;
}
}
App::historyUpdateDependent(this);
}
void HistoryMessage::updateMedia(const MTPMessageMedia *media) {
@ -7154,36 +7206,44 @@ void HistoryMessage::eraseFromOverview() {
}
}
QString HistoryMessage::selectedText(TextSelection selection) const {
QString result, textResult, mediaResult;
TextWithEntities HistoryMessage::selectedText(TextSelection selection) const {
TextWithEntities result, textResult, mediaResult;
if (selection == FullSelection) {
textResult = _text.original(AllTextSelection, Text::ExpandLinksAll);
textResult = _text.originalTextWithEntities(AllTextSelection, ExpandLinksAll);
} else {
textResult = _text.original(selection, Text::ExpandLinksAll);
textResult = _text.originalTextWithEntities(selection, ExpandLinksAll);
}
if (_media) {
mediaResult = _media->selectedText(toMediaSelection(selection));
}
if (textResult.isEmpty()) {
if (textResult.text.isEmpty()) {
result = mediaResult;
} else if (mediaResult.isEmpty()) {
} else if (mediaResult.text.isEmpty()) {
result = textResult;
} else {
result = textResult + qstr("\n\n") + mediaResult;
result.text = textResult.text + qstr("\n\n");
result.entities = textResult.entities;
appendTextWithEntities(result, std_::move(mediaResult));
}
if (auto fwd = Get<HistoryMessageForwarded>()) {
if (selection == FullSelection) {
QString fwdinfo = fwd->_text.original(AllTextSelection, Text::ExpandLinksAll), wrapped;
wrapped.reserve(fwdinfo.size() + 4 + result.size());
wrapped.append('[').append(fwdinfo).append(qsl("]\n")).append(result);
auto fwdinfo = fwd->_text.originalTextWithEntities(AllTextSelection, ExpandLinksAll);
TextWithEntities wrapped;
wrapped.text.reserve(fwdinfo.text.size() + 4 + result.text.size());
wrapped.entities.reserve(fwdinfo.entities.size() + result.entities.size());
wrapped.text.append('[');
appendTextWithEntities(wrapped, std_::move(fwdinfo));
wrapped.text.append(qsl("]\n"));
appendTextWithEntities(wrapped, std_::move(result));
result = wrapped;
}
}
if (auto reply = Get<HistoryMessageReply>()) {
if (selection == FullSelection && reply->replyToMsg) {
QString wrapped;
wrapped.reserve(lang(lng_in_reply_to).size() + reply->replyToMsg->author()->name.size() + 4 + result.size());
wrapped.append('[').append(lang(lng_in_reply_to)).append(' ').append(reply->replyToMsg->author()->name).append(qsl("]\n")).append(result);
TextWithEntities wrapped;
wrapped.text.reserve(lang(lng_in_reply_to).size() + reply->replyToMsg->author()->name.size() + 4 + result.text.size());
wrapped.text.append('[').append(lang(lng_in_reply_to)).append(' ').append(reply->replyToMsg->author()->name).append(qsl("]\n"));
appendTextWithEntities(wrapped, std_::move(result));
result = wrapped;
}
}
@ -7191,7 +7251,7 @@ QString HistoryMessage::selectedText(TextSelection selection) const {
}
QString HistoryMessage::inDialogsText() const {
return emptyText() ? (_media ? _media->inDialogsText() : QString()) : _text.original(AllTextSelection, Text::ExpandLinksNone);
return emptyText() ? (_media ? _media->inDialogsText() : QString()) : _text.originalText(AllTextSelection, ExpandLinksNone);
}
HistoryMedia *HistoryMessage::getMedia() const {
@ -7210,31 +7270,32 @@ void HistoryMessage::setMedia(const MTPMessageMedia *media) {
initMedia(media, t);
if (_media && _media->isDisplayed() && !mediaWasDisplayed) {
_text.removeSkipBlock();
_textWidth = 0;
_textWidth = -1;
_textHeight = 0;
} else if (mediaWasDisplayed && (!_media || !_media->isDisplayed())) {
_text.setSkipBlock(skipBlockWidth(), skipBlockHeight());
_textWidth = 0;
_textWidth = -1;
_textHeight = 0;
}
}
void HistoryMessage::setText(const QString &text, const EntitiesInText &entities) {
void HistoryMessage::setText(const TextWithEntities &textWithEntities) {
textstyleSet(&((out() && !isPost()) ? st::outTextStyle : st::inTextStyle));
if (_media && _media->isDisplayed()) {
_text.setMarkedText(st::msgFont, text, entities, itemTextOptions(this));
_text.setMarkedText(st::msgFont, textWithEntities, itemTextOptions(this));
} else {
_text.setMarkedText(st::msgFont, text + skipBlock(), entities, itemTextOptions(this));
_text.setMarkedText(st::msgFont, { textWithEntities.text + skipBlock(), textWithEntities.entities }, itemTextOptions(this));
}
textstyleRestore();
for (int32 i = 0, l = entities.size(); i != l; ++i) {
if (entities.at(i).type == EntityInTextUrl || entities.at(i).type == EntityInTextCustomUrl || entities.at(i).type == EntityInTextEmail) {
for_const (auto &entity, textWithEntities.entities) {
auto type = entity.type();
if (type == EntityInTextUrl || type == EntityInTextCustomUrl || type == EntityInTextEmail) {
_flags |= MTPDmessage_ClientFlag::f_has_text_links;
break;
}
}
_textWidth = 0;
_textWidth = -1;
_textHeight = 0;
}
@ -7282,15 +7343,14 @@ void HistoryMessage::setReplyMarkup(const MTPReplyMarkup *markup) {
}
}
QString HistoryMessage::originalText() const {
return emptyText() ? QString() : _text.original();
TextWithEntities HistoryMessage::originalText() const {
if (emptyText()) {
return { QString(), EntitiesInText() };
}
return _text.originalTextWithEntities();
}
EntitiesInText HistoryMessage::originalEntities() const {
return emptyText() ? EntitiesInText() : _text.originalEntities();
}
bool HistoryMessage::textHasLinks() {
bool HistoryMessage::textHasLinks() const {
return emptyText() ? false : _text.hasLinks();
}
@ -7393,7 +7453,7 @@ void HistoryMessage::setViewsCount(int32 count) {
} else {
if (_text.hasSkipBlock()) {
_text.setSkipBlock(HistoryMessage::skipBlockWidth(), HistoryMessage::skipBlockHeight());
_textWidth = 0;
_textWidth = -1;
_textHeight = 0;
}
setPendingInitDimensions();
@ -7408,7 +7468,7 @@ void HistoryMessage::setId(MsgId newId) {
} else {
if (_text.hasSkipBlock()) {
_text.setSkipBlock(HistoryMessage::skipBlockWidth(), HistoryMessage::skipBlockHeight());
_textWidth = 0;
_textWidth = -1;
_textHeight = 0;
}
setPendingInitDimensions();
@ -8077,7 +8137,6 @@ bool HistoryService::updatePinned(bool force) {
updatePinnedText();
}
if (force) {
setPendingInitDimensions();
if (gotDependencyItem && App::wnd()) {
App::wnd()->notifySettingGot();
}
@ -8111,7 +8170,7 @@ bool HistoryService::updatePinnedText(const QString *pfrom, QString *ptext) {
case MediaTypeVoiceFile: mediaText = lang(lng_action_pinned_media_voice); break;
}
if (mediaText.isEmpty()) {
QString original = pinned->msg->originalText();
QString original = pinned->msg->originalText().text;
int32 cutat = 0, limit = PinnedMessageTextLimit, size = original.size();
for (; limit > 0;) {
--limit;
@ -8163,7 +8222,7 @@ HistoryService::HistoryService(History *history, const MTPDmessageService &msg)
UpdateComponents(HistoryServicePinned::Bit());
MsgId pinnedMsgId = Get<HistoryServicePinned>()->msgId = msg.vreply_to_msg_id.v;
if (!updatePinned() && App::api()) {
App::api()->requestMessageData(history->peer->asChannel(), pinnedMsgId, new HistoryDependentItemCallback(fullId()));
App::api()->requestMessageData(history->peer->asChannel(), pinnedMsgId, std_::make_unique<HistoryDependentItemCallback>(fullId()));
}
}
setMessageByAction(msg.vaction);
@ -8189,12 +8248,12 @@ void HistoryService::countPositionAndSize(int32 &left, int32 &width) const {
width = maxwidth - st::msgServiceMargin.left() - st::msgServiceMargin.left();
}
QString HistoryService::selectedText(TextSelection selection) const {
return _text.original((selection == FullSelection) ? AllTextSelection : selection);
TextWithEntities HistoryService::selectedText(TextSelection selection) const {
return _text.originalTextWithEntities((selection == FullSelection) ? AllTextSelection : selection);
}
QString HistoryService::inDialogsText() const {
return _text.original(AllTextSelection, Text::ExpandLinksNone);
return _text.originalText(AllTextSelection, ExpandLinksNone);
}
QString HistoryService::inReplyText() const {
@ -8206,7 +8265,9 @@ void HistoryService::setServiceText(const QString &text) {
textstyleSet(&st::serviceTextStyle);
_text.setText(st::msgServiceFont, text, _historySrvOptions);
textstyleRestore();
initDimensions();
setPendingInitDimensions();
_textWidth = -1;
_textHeight = 0;
}
void HistoryService::draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const {
@ -8376,7 +8437,7 @@ void HistoryService::drawInDialog(Painter &p, const QRect &r, bool act, const Hi
}
QString HistoryService::notificationText() const {
QString msg = _text.original();
QString msg = _text.originalText();
if (msg.size() > 0xFF) msg = msg.mid(0, 0xFF) + qsl("...");
return msg;
}

View file

@ -151,22 +151,23 @@ struct SendAction {
int32 progress;
};
using TextWithTags = FlatTextarea::TextWithTags;
struct HistoryDraft {
HistoryDraft() : msgId(0), previewCancelled(false) {
}
HistoryDraft(const QString &text, MsgId msgId, const MessageCursor &cursor, bool previewCancelled)
: text(text)
HistoryDraft(const TextWithTags &textWithTags, MsgId msgId, const MessageCursor &cursor, bool previewCancelled)
: textWithTags(textWithTags)
, msgId(msgId)
, cursor(cursor)
, previewCancelled(previewCancelled) {
}
HistoryDraft(const FlatTextarea &field, MsgId msgId, bool previewCancelled)
: text(field.getLastText())
: textWithTags(field.getTextWithTags())
, msgId(msgId)
, cursor(field)
, previewCancelled(previewCancelled) {
}
QString text;
TextWithTags textWithTags;
MsgId msgId; // replyToId for message draft, editMsgId for edit draft
MessageCursor cursor;
bool previewCancelled;
@ -176,8 +177,8 @@ struct HistoryEditDraft : public HistoryDraft {
: HistoryDraft()
, saveRequest(0) {
}
HistoryEditDraft(const QString &text, MsgId msgId, const MessageCursor &cursor, bool previewCancelled, mtpRequestId saveRequest = 0)
: HistoryDraft(text, msgId, cursor, previewCancelled)
HistoryEditDraft(const TextWithTags &textWithTags, MsgId msgId, const MessageCursor &cursor, bool previewCancelled, mtpRequestId saveRequest = 0)
: HistoryDraft(textWithTags, msgId, cursor, previewCancelled)
, saveRequest(saveRequest) {
}
HistoryEditDraft(const FlatTextarea &field, MsgId msgId, bool previewCancelled, mtpRequestId saveRequest = 0)
@ -373,7 +374,7 @@ public:
}
void takeMsgDraft(History *from) {
if (auto &draft = from->_msgDraft) {
if (!draft->text.isEmpty() && !_msgDraft) {
if (!draft->textWithTags.text.isEmpty() && !_msgDraft) {
_msgDraft = std_::move(draft);
_msgDraft->msgId = 0; // edit and reply to drafts can't migrate
}
@ -1292,8 +1293,8 @@ public:
}
virtual void previousItemChanged();
virtual QString selectedText(TextSelection selection) const {
return qsl("[-]");
virtual TextWithEntities selectedText(TextSelection selection) const {
return { qsl("[-]"), EntitiesInText() };
}
virtual QString inDialogsText() const {
return qsl("-");
@ -1301,6 +1302,9 @@ public:
virtual QString inReplyText() const {
return inDialogsText();
}
virtual TextWithEntities originalText() const {
return { QString(), EntitiesInText() };
}
virtual void drawInfo(Painter &p, int32 right, int32 bottom, int32 width, bool selected, InfoDisplayType type) const {
}
@ -1362,15 +1366,9 @@ public:
virtual HistoryMedia *getMedia() const {
return nullptr;
}
virtual void setText(const QString &text, const EntitiesInText &links) {
virtual void setText(const TextWithEntities &textWithEntities) {
}
virtual QString originalText() const {
return QString();
}
virtual EntitiesInText originalEntities() const {
return EntitiesInText();
}
virtual bool textHasLinks() {
virtual bool textHasLinks() const {
return false;
}
@ -1526,8 +1524,8 @@ protected:
// to add required bits to the Composer mask
// after that always use Has<HistoryMessageDate>()
bool displayDate() const {
if (HistoryItem *prev = previous()) {
return prev->date.date().day() != date.date().day();
if (auto prev = previous()) {
return prev->date.date() != date.date();
}
return true;
}
@ -1566,7 +1564,8 @@ protected:
}
Text _text = { int(st::msgMinWidth) };
int32 _textWidth, _textHeight;
int _textWidth = -1;
int _textHeight = 0;
HistoryMediaPtr _media;
@ -1661,7 +1660,7 @@ public:
virtual HistoryMediaType type() const = 0;
virtual QString inDialogsText() const = 0;
virtual QString selectedText(TextSelection selection) const = 0;
virtual TextWithEntities selectedText(TextSelection selection) const = 0;
bool hasPoint(int x, int y) const {
return (x >= 0 && y >= 0 && x < _width && y < _height);
@ -1748,8 +1747,8 @@ public:
virtual ImagePtr replyPreview() {
return ImagePtr();
}
virtual QString getCaption() const {
return QString();
virtual TextWithEntities getCaption() const {
return TextWithEntities();
}
virtual bool needsBubble() const = 0;
virtual bool customInfoLayout() const = 0;
@ -1898,7 +1897,7 @@ public:
}
QString inDialogsText() const override;
QString selectedText(TextSelection selection) const override;
TextWithEntities selectedText(TextSelection selection) const override;
PhotoData *photo() const {
return _data;
@ -1915,8 +1914,8 @@ public:
}
ImagePtr replyPreview() override;
QString getCaption() const override {
return _caption.original();
TextWithEntities getCaption() const override {
return _caption.originalTextWithEntities();
}
bool needsBubble() const override {
if (!_caption.isEmpty()) {
@ -1978,7 +1977,7 @@ public:
}
QString inDialogsText() const override;
QString selectedText(TextSelection selection) const override;
TextWithEntities selectedText(TextSelection selection) const override;
DocumentData *getDocument() override {
return _data;
@ -1998,8 +1997,8 @@ public:
}
ImagePtr replyPreview() override;
QString getCaption() const override {
return _caption.original();
TextWithEntities getCaption() const override {
return _caption.originalTextWithEntities();
}
bool needsBubble() const override {
if (!_caption.isEmpty()) {
@ -2101,7 +2100,7 @@ public:
}
QString inDialogsText() const override;
QString selectedText(TextSelection selection) const override;
TextWithEntities selectedText(TextSelection selection) const override;
bool uploading() const override {
return _data->uploading();
@ -2122,11 +2121,11 @@ public:
}
ImagePtr replyPreview() override;
QString getCaption() const override {
TextWithEntities getCaption() const override {
if (const HistoryDocumentCaptioned *captioned = Get<HistoryDocumentCaptioned>()) {
return captioned->_caption.original();
return captioned->_caption.originalTextWithEntities();
}
return QString();
return TextWithEntities();
}
bool needsBubble() const override {
return true;
@ -2188,7 +2187,7 @@ public:
}
QString inDialogsText() const override;
QString selectedText(TextSelection selection) const override;
TextWithEntities selectedText(TextSelection selection) const override;
bool uploading() const override {
return _data->uploading();
@ -2215,8 +2214,8 @@ public:
}
ImagePtr replyPreview() override;
QString getCaption() const override {
return _caption.original();
TextWithEntities getCaption() const override {
return _caption.originalTextWithEntities();
}
bool needsBubble() const override {
if (!_caption.isEmpty()) {
@ -2286,7 +2285,7 @@ public:
}
QString inDialogsText() const override;
QString selectedText(TextSelection selection) const override;
TextWithEntities selectedText(TextSelection selection) const override;
DocumentData *getDocument() override {
return _data;
@ -2355,7 +2354,7 @@ public:
}
QString inDialogsText() const override;
QString selectedText(TextSelection selection) const override;
TextWithEntities selectedText(TextSelection selection) const override;
void attachToParent() override;
void detachFromParent() override;
@ -2423,7 +2422,7 @@ public:
}
QString inDialogsText() const override;
QString selectedText(TextSelection selection) const override;
TextWithEntities selectedText(TextSelection selection) const override;
void clickHandlerActiveChanged(const ClickHandlerPtr &p, bool active) override;
void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override;
@ -2557,7 +2556,7 @@ public:
}
QString inDialogsText() const override;
QString selectedText(TextSelection selection) const override;
TextWithEntities selectedText(TextSelection selection) const override;
bool needsBubble() const override {
if (!_title.isEmpty() || !_description.isEmpty()) {
@ -2611,8 +2610,8 @@ public:
static HistoryMessage *create(History *history, MsgId msgId, MTPDmessage::Flags flags, QDateTime date, int32 from, HistoryMessage *fwd) {
return _create(history, msgId, flags, date, from, fwd);
}
static HistoryMessage *create(History *history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, int32 viaBotId, QDateTime date, int32 from, const QString &msg, const EntitiesInText &entities) {
return _create(history, msgId, flags, replyTo, viaBotId, date, from, msg, entities);
static HistoryMessage *create(History *history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, int32 viaBotId, QDateTime date, int32 from, const TextWithEntities &textWithEntities) {
return _create(history, msgId, flags, replyTo, viaBotId, date, from, textWithEntities);
}
static HistoryMessage *create(History *history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, int32 viaBotId, QDateTime date, int32 from, DocumentData *doc, const QString &caption, const MTPReplyMarkup &markup) {
return _create(history, msgId, flags, replyTo, viaBotId, date, from, doc, caption, markup);
@ -2683,13 +2682,12 @@ public:
int32 addToOverview(AddToOverviewMethod method) override;
void eraseFromOverview();
QString selectedText(TextSelection selection) const override;
TextWithEntities selectedText(TextSelection selection) const override;
QString inDialogsText() const override;
HistoryMedia *getMedia() const override;
void setText(const QString &text, const EntitiesInText &entities) override;
QString originalText() const override;
EntitiesInText originalEntities() const override;
bool textHasLinks() override;
void setText(const TextWithEntities &textWithEntities) override;
TextWithEntities originalText() const override;
bool textHasLinks() const override;
int32 infoWidth() const override {
int32 result = _timeWidth;
@ -2751,7 +2749,7 @@ private:
HistoryMessage(History *history, const MTPDmessage &msg);
HistoryMessage(History *history, MsgId msgId, MTPDmessage::Flags flags, QDateTime date, int32 from, HistoryMessage *fwd); // local forwarded
HistoryMessage(History *history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, int32 viaBotId, QDateTime date, int32 from, const QString &msg, const EntitiesInText &entities); // local message
HistoryMessage(History *history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, int32 viaBotId, QDateTime date, int32 from, const TextWithEntities &textWithEntities); // local message
HistoryMessage(History *history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, int32 viaBotId, QDateTime date, int32 from, DocumentData *doc, const QString &caption, const MTPReplyMarkup &markup); // local document
HistoryMessage(History *history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, int32 viaBotId, QDateTime date, int32 from, PhotoData *photo, const QString &caption, const MTPReplyMarkup &markup); // local photo
friend class HistoryItemInstantiated<HistoryMessage>;
@ -2820,25 +2818,6 @@ inline MTPDmessage::Flags newMessageFlags(PeerData *p) {
}
return result;
}
inline MTPDmessage::Flags newForwardedFlags(PeerData *p, int32 from, HistoryMessage *fwd) {
MTPDmessage::Flags result = newMessageFlags(p);
if (from) {
result |= MTPDmessage::Flag::f_from_id;
}
if (fwd->Has<HistoryMessageVia>()) {
result |= MTPDmessage::Flag::f_via_bot_id;
}
if (!p->isChannel()) {
if (HistoryMedia *media = fwd->getMedia()) {
if (media->type() == MediaTypeVoiceFile) {
result |= MTPDmessage::Flag::f_media_unread;
// } else if (media->type() == MediaTypeVideo) {
// result |= MTPDmessage::flag_media_unread;
}
}
}
return result;
}
struct HistoryServicePinned : public BaseComponent<HistoryServicePinned> {
MsgId msgId = 0;
@ -2900,7 +2879,7 @@ public:
bool serviceMsg() const override {
return true;
}
QString selectedText(TextSelection selection) const override;
TextWithEntities selectedText(TextSelection selection) const override;
QString inDialogsText() const override;
QString inReplyText() const override;
@ -2937,8 +2916,8 @@ public:
HistoryTextState getState(int x, int y, HistoryStateRequest request) const override;
QString selectedText(TextSelection selection) const override {
return QString();
TextWithEntities selectedText(TextSelection selection) const override {
return { QString(), EntitiesInText() };
}
HistoryItemType type() const override {
return HistoryItemGroup;
@ -2987,8 +2966,8 @@ public:
void draw(Painter &p, const QRect &r, TextSelection selection, uint64 ms) const override;
HistoryTextState getState(int x, int y, HistoryStateRequest request) const override;
QString selectedText(TextSelection selection) const override {
return QString();
TextWithEntities selectedText(TextSelection selection) const override {
return { QString(), EntitiesInText() };
}
HistoryItemType type() const override {
return HistoryItemCollapse;

View file

@ -0,0 +1,938 @@
/*
This file is part of Telegram Desktop,
the official desktop version of Telegram messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
It is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
*/
#include "stdafx.h"
#include "history/field_autocomplete.h"
#include "mainwindow.h"
#include "apiwrap.h"
#include "localstorage.h"
FieldAutocomplete::FieldAutocomplete(QWidget *parent) : TWidget(parent)
, _scroll(this, st::mentionScroll)
, _inner(this, &_mrows, &_hrows, &_brows, &_srows)
, _chat(0)
, _user(0)
, _channel(0)
, _hiding(false)
, a_opacity(0)
, _a_appearance(animation(this, &FieldAutocomplete::step_appearance))
, _shadow(st::dropdownDef.shadow) {
_hideTimer.setSingleShot(true);
connect(&_hideTimer, SIGNAL(timeout()), this, SLOT(hideStart()));
connect(_inner, SIGNAL(mentionChosen(UserData*,FieldAutocomplete::ChooseMethod)), this, SIGNAL(mentionChosen(UserData*,FieldAutocomplete::ChooseMethod)));
connect(_inner, SIGNAL(hashtagChosen(QString,FieldAutocomplete::ChooseMethod)), this, SIGNAL(hashtagChosen(QString,FieldAutocomplete::ChooseMethod)));
connect(_inner, SIGNAL(botCommandChosen(QString,FieldAutocomplete::ChooseMethod)), this, SIGNAL(botCommandChosen(QString,FieldAutocomplete::ChooseMethod)));
connect(_inner, SIGNAL(stickerChosen(DocumentData*,FieldAutocomplete::ChooseMethod)), this, SIGNAL(stickerChosen(DocumentData*,FieldAutocomplete::ChooseMethod)));
connect(_inner, SIGNAL(mustScrollTo(int, int)), _scroll, SLOT(scrollToY(int, int)));
connect(App::wnd(), SIGNAL(imageLoaded()), _inner, SLOT(update()));
setFocusPolicy(Qt::NoFocus);
_scroll->setFocusPolicy(Qt::NoFocus);
_scroll->viewport()->setFocusPolicy(Qt::NoFocus);
_inner->setGeometry(rect());
_scroll->setGeometry(rect());
_scroll->setWidget(_inner);
_scroll->show();
_inner->show();
connect(_scroll, SIGNAL(geometryChanged()), _inner, SLOT(onParentGeometryChanged()));
connect(_scroll, SIGNAL(scrolled()), _inner, SLOT(onUpdateSelected()));
}
void FieldAutocomplete::paintEvent(QPaintEvent *e) {
Painter p(this);
if (_a_appearance.animating()) {
p.setOpacity(a_opacity.current());
p.drawPixmap(0, 0, _cache);
return;
}
p.fillRect(rect(), st::white);
}
void FieldAutocomplete::showFiltered(PeerData *peer, QString query, bool addInlineBots) {
_chat = peer->asChat();
_user = peer->asUser();
_channel = peer->asChannel();
if (query.isEmpty()) {
_type = Type::Mentions;
rowsUpdated(internal::MentionRows(), internal::HashtagRows(), internal::BotCommandRows(), _srows, false);
return;
}
_emoji = EmojiPtr();
query = query.toLower();
auto type = Type::Stickers;
auto plainQuery = query.midRef(0);
switch (query.at(0).unicode()) {
case '@':
type = Type::Mentions;
plainQuery = query.midRef(1);
break;
case '#':
type = Type::Hashtags;
plainQuery = query.midRef(1);
break;
case '/':
type = Type::BotCommands;
plainQuery = query.midRef(1);
break;
}
bool resetScroll = (_type != type || _filter != plainQuery);
if (resetScroll) {
_type = type;
_filter = plainQuery.toString();
}
_addInlineBots = addInlineBots;
updateFiltered(resetScroll);
}
void FieldAutocomplete::showStickers(EmojiPtr emoji) {
bool resetScroll = (_emoji != emoji);
_emoji = emoji;
_type = Type::Stickers;
if (!emoji) {
rowsUpdated(_mrows, _hrows, _brows, StickerPack(), false);
return;
}
_chat = 0;
_user = 0;
_channel = 0;
updateFiltered(resetScroll);
}
bool FieldAutocomplete::clearFilteredBotCommands() {
if (_brows.isEmpty()) return false;
_brows.clear();
return true;
}
namespace {
template <typename T, typename U>
inline int indexOfInFirstN(const T &v, const U &elem, int last) {
for (auto b = v.cbegin(), i = b, e = b + qMax(v.size(), last); i != e; ++i) {
if (*i == elem) {
return (i - b);
}
}
return -1;
}
}
void FieldAutocomplete::updateFiltered(bool resetScroll) {
int32 now = unixtime(), recentInlineBots = 0;
internal::MentionRows mrows;
internal::HashtagRows hrows;
internal::BotCommandRows brows;
StickerPack srows;
if (_emoji) {
QMap<uint64, uint64> setsToRequest;
Stickers::Sets &sets(Global::RefStickerSets());
const Stickers::Order &order(Global::StickerSetsOrder());
for (int i = 0, l = order.size(); i < l; ++i) {
auto it = sets.find(order.at(i));
if (it != sets.cend()) {
if (it->emoji.isEmpty()) {
setsToRequest.insert(it->id, it->access);
it->flags |= MTPDstickerSet_ClientFlag::f_not_loaded;
} else if (!(it->flags & MTPDstickerSet::Flag::f_disabled)) {
StickersByEmojiMap::const_iterator i = it->emoji.constFind(emojiGetNoColor(_emoji));
if (i != it->emoji.cend()) {
srows += *i;
}
}
}
}
if (!setsToRequest.isEmpty() && App::api()) {
for (QMap<uint64, uint64>::const_iterator i = setsToRequest.cbegin(), e = setsToRequest.cend(); i != e; ++i) {
App::api()->scheduleStickerSetRequest(i.key(), i.value());
}
App::api()->requestStickerSets();
}
} else if (_type == Type::Mentions) {
int maxListSize = _addInlineBots ? cRecentInlineBots().size() : 0;
if (_chat) {
maxListSize += (_chat->participants.isEmpty() ? _chat->lastAuthors.size() : _chat->participants.size());
} else if (_channel && _channel->isMegagroup()) {
if (_channel->mgInfo->lastParticipants.isEmpty() || _channel->lastParticipantsCountOutdated()) {
} else {
maxListSize += _channel->mgInfo->lastParticipants.size();
}
}
if (maxListSize) {
mrows.reserve(maxListSize);
}
auto filterNotPassedByUsername = [this](UserData *user) -> bool {
if (user->username.startsWith(_filter, Qt::CaseInsensitive)) {
bool exactUsername = (user->username.size() == _filter.size());
return exactUsername;
}
return true;
};
auto filterNotPassedByName = [this](UserData *user) -> bool {
for_const (auto &namePart, user->names) {
if (namePart.startsWith(_filter, Qt::CaseInsensitive)) {
bool exactUsername = (user->username.compare(_filter, Qt::CaseInsensitive) == 0);
return exactUsername;
}
}
return true;
};
bool listAllSuggestions = _filter.isEmpty();
if (_addInlineBots) {
for_const (auto user, cRecentInlineBots()) {
if (!listAllSuggestions && filterNotPassedByUsername(user)) continue;
mrows.push_back(user);
++recentInlineBots;
}
}
if (_chat) {
QMultiMap<int32, UserData*> ordered;
mrows.reserve(mrows.size() + (_chat->participants.isEmpty() ? _chat->lastAuthors.size() : _chat->participants.size()));
if (_chat->noParticipantInfo()) {
if (App::api()) App::api()->requestFullPeer(_chat);
} else if (!_chat->participants.isEmpty()) {
for (auto i = _chat->participants.cbegin(), e = _chat->participants.cend(); i != e; ++i) {
UserData *user = i.key();
if (!listAllSuggestions && filterNotPassedByName(user)) continue;
if (indexOfInFirstN(mrows, user, recentInlineBots) >= 0) continue;
ordered.insertMulti(App::onlineForSort(user, now), user);
}
}
for_const (auto user, _chat->lastAuthors) {
if (!listAllSuggestions && filterNotPassedByName(user)) continue;
if (indexOfInFirstN(mrows, user, recentInlineBots) >= 0) continue;
mrows.push_back(user);
if (!ordered.isEmpty()) {
ordered.remove(App::onlineForSort(user, now), user);
}
}
if (!ordered.isEmpty()) {
for (QMultiMap<int32, UserData*>::const_iterator i = ordered.cend(), b = ordered.cbegin(); i != b;) {
--i;
mrows.push_back(i.value());
}
}
} else if (_channel && _channel->isMegagroup()) {
QMultiMap<int32, UserData*> ordered;
if (_channel->mgInfo->lastParticipants.isEmpty() || _channel->lastParticipantsCountOutdated()) {
if (App::api()) App::api()->requestLastParticipants(_channel);
} else {
mrows.reserve(mrows.size() + _channel->mgInfo->lastParticipants.size());
for_const (auto user, _channel->mgInfo->lastParticipants) {
if (!listAllSuggestions && filterNotPassedByName(user)) continue;
if (indexOfInFirstN(mrows, user, recentInlineBots) >= 0) continue;
mrows.push_back(user);
}
}
}
} else if (_type == Type::Hashtags) {
bool listAllSuggestions = _filter.isEmpty();
auto &recent(cRecentWriteHashtags());
hrows.reserve(recent.size());
for (auto i = recent.cbegin(), e = recent.cend(); i != e; ++i) {
if (!listAllSuggestions && (!i->first.startsWith(_filter, Qt::CaseInsensitive) || i->first.size() == _filter.size())) continue;
hrows.push_back(i->first);
}
} else if (_type == Type::BotCommands) {
bool listAllSuggestions = _filter.isEmpty();
bool hasUsername = _filter.indexOf('@') > 0;
QMap<UserData*, bool> bots;
int32 cnt = 0;
if (_chat) {
if (_chat->noParticipantInfo()) {
if (App::api()) App::api()->requestFullPeer(_chat);
} else if (!_chat->participants.isEmpty()) {
for (auto i = _chat->participants.cbegin(), e = _chat->participants.cend(); i != e; ++i) {
UserData *user = i.key();
if (!user->botInfo) continue;
if (!user->botInfo->inited && App::api()) App::api()->requestFullPeer(user);
if (user->botInfo->commands.isEmpty()) continue;
bots.insert(user, true);
cnt += user->botInfo->commands.size();
}
}
} else if (_user && _user->botInfo) {
if (!_user->botInfo->inited && App::api()) App::api()->requestFullPeer(_user);
cnt = _user->botInfo->commands.size();
bots.insert(_user, true);
} else if (_channel && _channel->isMegagroup()) {
if (_channel->mgInfo->bots.isEmpty()) {
if (!_channel->mgInfo->botStatus && App::api()) App::api()->requestBots(_channel);
} else {
for_const (auto user, _channel->mgInfo->bots) {
if (!user->botInfo) continue;
if (!user->botInfo->inited && App::api()) App::api()->requestFullPeer(user);
if (user->botInfo->commands.isEmpty()) continue;
bots.insert(user, true);
cnt += user->botInfo->commands.size();
}
}
}
if (cnt) {
brows.reserve(cnt);
int32 botStatus = _chat ? _chat->botStatus : ((_channel && _channel->isMegagroup()) ? _channel->mgInfo->botStatus : -1);
if (_chat) {
for (auto i = _chat->lastAuthors.cbegin(), e = _chat->lastAuthors.cend(); i != e; ++i) {
UserData *user = *i;
if (!user->botInfo) continue;
if (!bots.contains(user)) continue;
if (!user->botInfo->inited && App::api()) App::api()->requestFullPeer(user);
if (user->botInfo->commands.isEmpty()) continue;
bots.remove(user);
for (int32 j = 0, l = user->botInfo->commands.size(); j < l; ++j) {
if (!listAllSuggestions) {
QString toFilter = (hasUsername || botStatus == 0 || botStatus == 2) ? user->botInfo->commands.at(j).command + '@' + user->username : user->botInfo->commands.at(j).command;
if (!toFilter.startsWith(_filter, Qt::CaseInsensitive)/* || toFilter.size() == _filter.size()*/) continue;
}
brows.push_back(qMakePair(user, &user->botInfo->commands.at(j)));
}
}
}
if (!bots.isEmpty()) {
for (QMap<UserData*, bool>::const_iterator i = bots.cbegin(), e = bots.cend(); i != e; ++i) {
UserData *user = i.key();
for (int32 j = 0, l = user->botInfo->commands.size(); j < l; ++j) {
if (!listAllSuggestions) {
QString toFilter = (hasUsername || botStatus == 0 || botStatus == 2) ? user->botInfo->commands.at(j).command + '@' + user->username : user->botInfo->commands.at(j).command;
if (!toFilter.startsWith(_filter, Qt::CaseInsensitive)/* || toFilter.size() == _filter.size()*/) continue;
}
brows.push_back(qMakePair(user, &user->botInfo->commands.at(j)));
}
}
}
}
}
rowsUpdated(mrows, hrows, brows, srows, resetScroll);
_inner->setRecentInlineBotsInRows(recentInlineBots);
}
void FieldAutocomplete::rowsUpdated(const internal::MentionRows &mrows, const internal::HashtagRows &hrows, const internal::BotCommandRows &brows, const StickerPack &srows, bool resetScroll) {
if (mrows.isEmpty() && hrows.isEmpty() && brows.isEmpty() && srows.isEmpty()) {
if (!isHidden()) {
hideStart();
}
_mrows.clear();
_hrows.clear();
_brows.clear();
_srows.clear();
} else {
_mrows = mrows;
_hrows = hrows;
_brows = brows;
_srows = srows;
bool hidden = _hiding || isHidden();
if (hidden) {
show();
_scroll->show();
}
recount(resetScroll);
update();
if (hidden) {
hide();
showStart();
}
}
}
void FieldAutocomplete::setBoundings(QRect boundings) {
_boundings = boundings;
recount();
}
void FieldAutocomplete::recount(bool resetScroll) {
int32 h = 0, oldst = _scroll->scrollTop(), st = oldst, maxh = 4.5 * st::mentionHeight;
if (!_srows.isEmpty()) {
int32 stickersPerRow = qMax(1, int32(_boundings.width() - 2 * st::stickerPanPadding) / int32(st::stickerPanSize.width()));
int32 rows = rowscount(_srows.size(), stickersPerRow);
h = st::stickerPanPadding + rows * st::stickerPanSize.height();
} else if (!_mrows.isEmpty()) {
h = _mrows.size() * st::mentionHeight;
} else if (!_hrows.isEmpty()) {
h = _hrows.size() * st::mentionHeight;
} else if (!_brows.isEmpty()) {
h = _brows.size() * st::mentionHeight;
}
if (_inner->width() != _boundings.width() || _inner->height() != h) {
_inner->resize(_boundings.width(), h);
}
if (h > _boundings.height()) h = _boundings.height();
if (h > maxh) h = maxh;
if (width() != _boundings.width() || height() != h) {
setGeometry(_boundings.x(), _boundings.y() + _boundings.height() - h, _boundings.width(), h);
_scroll->resize(_boundings.width(), h);
} else if (y() != _boundings.y() + _boundings.height() - h) {
move(_boundings.x(), _boundings.y() + _boundings.height() - h);
}
if (resetScroll) st = 0;
if (st != oldst) _scroll->scrollToY(st);
if (resetScroll) _inner->clearSel();
}
void FieldAutocomplete::fastHide() {
if (_a_appearance.animating()) {
_a_appearance.stop();
}
a_opacity = anim::fvalue(0, 0);
_hideTimer.stop();
hideFinish();
}
void FieldAutocomplete::hideStart() {
if (!_hiding) {
if (_cache.isNull()) {
_scroll->show();
_cache = myGrab(this);
}
_scroll->hide();
_hiding = true;
a_opacity.start(0);
setAttribute(Qt::WA_OpaquePaintEvent, false);
_a_appearance.start();
}
}
void FieldAutocomplete::hideFinish() {
hide();
_hiding = false;
_filter = qsl("-");
_inner->clearSel(true);
}
void FieldAutocomplete::showStart() {
if (!isHidden() && a_opacity.current() == 1 && !_hiding) {
return;
}
if (_cache.isNull()) {
_scroll->show();
_cache = myGrab(this);
}
_scroll->hide();
_hiding = false;
show();
a_opacity.start(1);
setAttribute(Qt::WA_OpaquePaintEvent, false);
_a_appearance.start();
}
void FieldAutocomplete::step_appearance(float64 ms, bool timer) {
float64 dt = ms / st::dropdownDef.duration;
if (dt >= 1) {
_a_appearance.stop();
a_opacity.finish();
_cache = QPixmap();
setAttribute(Qt::WA_OpaquePaintEvent);
if (_hiding) {
hideFinish();
} else {
_scroll->show();
_inner->clearSel();
}
} else {
a_opacity.update(dt, anim::linear);
}
if (timer) update();
}
const QString &FieldAutocomplete::filter() const {
return _filter;
}
ChatData *FieldAutocomplete::chat() const {
return _chat;
}
ChannelData *FieldAutocomplete::channel() const {
return _channel;
}
UserData *FieldAutocomplete::user() const {
return _user;
}
int32 FieldAutocomplete::innerTop() {
return _scroll->scrollTop();
}
int32 FieldAutocomplete::innerBottom() {
return _scroll->scrollTop() + _scroll->height();
}
bool FieldAutocomplete::chooseSelected(ChooseMethod method) const {
return _inner->chooseSelected(method);
}
bool FieldAutocomplete::eventFilter(QObject *obj, QEvent *e) {
if (isHidden()) return QWidget::eventFilter(obj, e);
if (e->type() == QEvent::KeyPress) {
QKeyEvent *ev = static_cast<QKeyEvent*>(e);
if (!(ev->modifiers() & (Qt::AltModifier | Qt::ControlModifier | Qt::ShiftModifier | Qt::MetaModifier))) {
if (ev->key() == Qt::Key_Up || ev->key() == Qt::Key_Down || (!_srows.isEmpty() && (ev->key() == Qt::Key_Left || ev->key() == Qt::Key_Right))) {
return _inner->moveSel(ev->key());
} else if (ev->key() == Qt::Key_Enter || ev->key() == Qt::Key_Return) {
return _inner->chooseSelected(ChooseMethod::ByEnter);
}
}
}
return QWidget::eventFilter(obj, e);
}
FieldAutocomplete::~FieldAutocomplete() {
}
namespace internal {
FieldAutocompleteInner::FieldAutocompleteInner(FieldAutocomplete *parent, MentionRows *mrows, HashtagRows *hrows, BotCommandRows *brows, StickerPack *srows)
: _parent(parent)
, _mrows(mrows)
, _hrows(hrows)
, _brows(brows)
, _srows(srows)
, _stickersPerRow(1)
, _recentInlineBotsInRows(0)
, _sel(-1)
, _down(-1)
, _mouseSel(false)
, _overDelete(false)
, _previewShown(false) {
_previewTimer.setSingleShot(true);
connect(&_previewTimer, SIGNAL(timeout()), this, SLOT(onPreview()));
}
void FieldAutocompleteInner::paintEvent(QPaintEvent *e) {
Painter p(this);
QRect r(e->rect());
if (r != rect()) p.setClipRect(r);
int32 atwidth = st::mentionFont->width('@'), hashwidth = st::mentionFont->width('#');
int32 mentionleft = 2 * st::mentionPadding.left() + st::mentionPhotoSize;
int32 mentionwidth = width() - mentionleft - 2 * st::mentionPadding.right();
int32 htagleft = st::btnAttachPhoto.width + st::taMsgField.textMrg.left() - st::lineWidth, htagwidth = width() - st::mentionPadding.right() - htagleft - st::mentionScroll.width;
if (!_srows->isEmpty()) {
int32 rows = rowscount(_srows->size(), _stickersPerRow);
int32 fromrow = floorclamp(r.y() - st::stickerPanPadding, st::stickerPanSize.height(), 0, rows);
int32 torow = ceilclamp(r.y() + r.height() - st::stickerPanPadding, st::stickerPanSize.height(), 0, rows);
int32 fromcol = floorclamp(r.x() - st::stickerPanPadding, st::stickerPanSize.width(), 0, _stickersPerRow);
int32 tocol = ceilclamp(r.x() + r.width() - st::stickerPanPadding, st::stickerPanSize.width(), 0, _stickersPerRow);
for (int32 row = fromrow; row < torow; ++row) {
for (int32 col = fromcol; col < tocol; ++col) {
int32 index = row * _stickersPerRow + col;
if (index >= _srows->size()) break;
DocumentData *sticker = _srows->at(index);
if (!sticker->sticker()) continue;
QPoint pos(st::stickerPanPadding + col * st::stickerPanSize.width(), st::stickerPanPadding + row * st::stickerPanSize.height());
if (_sel == index) {
QPoint tl(pos);
if (rtl()) tl.setX(width() - tl.x() - st::stickerPanSize.width());
App::roundRect(p, QRect(tl, st::stickerPanSize), st::emojiPanHover, StickerHoverCorners);
}
bool goodThumb = !sticker->thumb->isNull() && ((sticker->thumb->width() >= 128) || (sticker->thumb->height() >= 128));
if (goodThumb) {
sticker->thumb->load();
} else {
sticker->checkSticker();
}
float64 coef = qMin((st::stickerPanSize.width() - st::msgRadius * 2) / float64(sticker->dimensions.width()), (st::stickerPanSize.height() - st::msgRadius * 2) / float64(sticker->dimensions.height()));
if (coef > 1) coef = 1;
int32 w = qRound(coef * sticker->dimensions.width()), h = qRound(coef * sticker->dimensions.height());
if (w < 1) w = 1;
if (h < 1) h = 1;
QPoint ppos = pos + QPoint((st::stickerPanSize.width() - w) / 2, (st::stickerPanSize.height() - h) / 2);
if (goodThumb) {
p.drawPixmapLeft(ppos, width(), sticker->thumb->pix(w, h));
} else if (!sticker->sticker()->img->isNull()) {
p.drawPixmapLeft(ppos, width(), sticker->sticker()->img->pix(w, h));
}
}
}
} else {
int32 from = qFloor(e->rect().top() / st::mentionHeight), to = qFloor(e->rect().bottom() / st::mentionHeight) + 1;
int32 last = _mrows->isEmpty() ? (_hrows->isEmpty() ? _brows->size() : _hrows->size()) : _mrows->size();
auto filter = _parent->filter();
bool hasUsername = filter.indexOf('@') > 0;
int filterSize = filter.size();
bool filterIsEmpty = filter.isEmpty();
for (int32 i = from; i < to; ++i) {
if (i >= last) break;
bool selected = (i == _sel);
if (selected) {
p.fillRect(0, i * st::mentionHeight, width(), st::mentionHeight, st::mentionBgOver->b);
int skip = (st::mentionHeight - st::notifyClose.icon.pxHeight()) / 2;
if (!_hrows->isEmpty() || (!_mrows->isEmpty() && i < _recentInlineBotsInRows)) {
p.drawSprite(QPoint(width() - st::notifyClose.icon.pxWidth() - skip, i * st::mentionHeight + skip), st::notifyClose.icon);
}
}
p.setPen(st::black->p);
if (!_mrows->isEmpty()) {
UserData *user = _mrows->at(i);
QString first = (!filterIsEmpty && user->username.startsWith(filter, Qt::CaseInsensitive)) ? ('@' + user->username.mid(0, filterSize)) : QString();
QString second = first.isEmpty() ? (user->username.isEmpty() ? QString() : ('@' + user->username)) : user->username.mid(filterSize);
int32 firstwidth = st::mentionFont->width(first), secondwidth = st::mentionFont->width(second), unamewidth = firstwidth + secondwidth, namewidth = user->nameText.maxWidth();
if (mentionwidth < unamewidth + namewidth) {
namewidth = (mentionwidth * namewidth) / (namewidth + unamewidth);
unamewidth = mentionwidth - namewidth;
if (firstwidth < unamewidth + st::mentionFont->elidew) {
if (firstwidth < unamewidth) {
first = st::mentionFont->elided(first, unamewidth);
} else if (!second.isEmpty()) {
first = st::mentionFont->elided(first + second, unamewidth);
second = QString();
}
} else {
second = st::mentionFont->elided(second, unamewidth - firstwidth);
}
}
user->loadUserpic();
user->paintUserpicLeft(p, st::mentionPhotoSize, st::mentionPadding.left(), i * st::mentionHeight + st::mentionPadding.top(), width());
user->nameText.drawElided(p, 2 * st::mentionPadding.left() + st::mentionPhotoSize, i * st::mentionHeight + st::mentionTop, namewidth);
p.setFont(st::mentionFont->f);
p.setPen((selected ? st::mentionFgOverActive : st::mentionFgActive)->p);
p.drawText(mentionleft + namewidth + st::mentionPadding.right(), i * st::mentionHeight + st::mentionTop + st::mentionFont->ascent, first);
if (!second.isEmpty()) {
p.setPen((selected ? st::mentionFgOver : st::mentionFg)->p);
p.drawText(mentionleft + namewidth + st::mentionPadding.right() + firstwidth, i * st::mentionHeight + st::mentionTop + st::mentionFont->ascent, second);
}
} else if (!_hrows->isEmpty()) {
QString hrow = _hrows->at(i);
QString first = filterIsEmpty ? QString() : ('#' + hrow.mid(0, filterSize));
QString second = filterIsEmpty ? ('#' + hrow) : hrow.mid(filterSize);
int32 firstwidth = st::mentionFont->width(first), secondwidth = st::mentionFont->width(second);
if (htagwidth < firstwidth + secondwidth) {
if (htagwidth < firstwidth + st::mentionFont->elidew) {
first = st::mentionFont->elided(first + second, htagwidth);
second = QString();
} else {
second = st::mentionFont->elided(second, htagwidth - firstwidth);
}
}
p.setFont(st::mentionFont->f);
if (!first.isEmpty()) {
p.setPen((selected ? st::mentionFgOverActive : st::mentionFgActive)->p);
p.drawText(htagleft, i * st::mentionHeight + st::mentionTop + st::mentionFont->ascent, first);
}
if (!second.isEmpty()) {
p.setPen((selected ? st::mentionFgOver : st::mentionFg)->p);
p.drawText(htagleft + firstwidth, i * st::mentionHeight + st::mentionTop + st::mentionFont->ascent, second);
}
} else {
UserData *user = _brows->at(i).first;
const BotCommand *command = _brows->at(i).second;
QString toHighlight = command->command;
int32 botStatus = _parent->chat() ? _parent->chat()->botStatus : ((_parent->channel() && _parent->channel()->isMegagroup()) ? _parent->channel()->mgInfo->botStatus : -1);
if (hasUsername || botStatus == 0 || botStatus == 2) {
toHighlight += '@' + user->username;
}
user->loadUserpic();
user->paintUserpicLeft(p, st::mentionPhotoSize, st::mentionPadding.left(), i * st::mentionHeight + st::mentionPadding.top(), width());
int32 addleft = 0, widthleft = mentionwidth;
QString first = filterIsEmpty ? QString() : ('/' + toHighlight.mid(0, filterSize));
QString second = filterIsEmpty ? ('/' + toHighlight) : toHighlight.mid(filterSize);
int32 firstwidth = st::mentionFont->width(first), secondwidth = st::mentionFont->width(second);
if (widthleft < firstwidth + secondwidth) {
if (widthleft < firstwidth + st::mentionFont->elidew) {
first = st::mentionFont->elided(first + second, widthleft);
second = QString();
} else {
second = st::mentionFont->elided(second, widthleft - firstwidth);
}
}
p.setFont(st::mentionFont->f);
if (!first.isEmpty()) {
p.setPen((selected ? st::mentionFgOverActive : st::mentionFgActive)->p);
p.drawText(mentionleft, i * st::mentionHeight + st::mentionTop + st::mentionFont->ascent, first);
}
if (!second.isEmpty()) {
p.setPen((selected ? st::mentionFgOver : st::mentionFg)->p);
p.drawText(mentionleft + firstwidth, i * st::mentionHeight + st::mentionTop + st::mentionFont->ascent, second);
}
addleft += firstwidth + secondwidth + st::mentionPadding.left();
widthleft -= firstwidth + secondwidth + st::mentionPadding.left();
if (widthleft > st::mentionFont->elidew && !command->descriptionText().isEmpty()) {
p.setPen((selected ? st::mentionFgOver : st::mentionFg)->p);
command->descriptionText().drawElided(p, mentionleft + addleft, i * st::mentionHeight + st::mentionTop, widthleft, 1, style::al_right);
}
}
}
p.fillRect(Adaptive::OneColumn() ? 0 : st::lineWidth, _parent->innerBottom() - st::lineWidth, width() - (Adaptive::OneColumn() ? 0 : st::lineWidth), st::lineWidth, st::shadowColor->b);
}
p.fillRect(Adaptive::OneColumn() ? 0 : st::lineWidth, _parent->innerTop(), width() - (Adaptive::OneColumn() ? 0 : st::lineWidth), st::lineWidth, st::shadowColor->b);
}
void FieldAutocompleteInner::resizeEvent(QResizeEvent *e) {
_stickersPerRow = qMax(1, int32(width() - 2 * st::stickerPanPadding) / int32(st::stickerPanSize.width()));
}
void FieldAutocompleteInner::mouseMoveEvent(QMouseEvent *e) {
_mousePos = mapToGlobal(e->pos());
_mouseSel = true;
onUpdateSelected(true);
}
void FieldAutocompleteInner::clearSel(bool hidden) {
_mouseSel = _overDelete = false;
setSel((_mrows->isEmpty() && _brows->isEmpty() && _hrows->isEmpty()) ? -1 : 0);
if (hidden) {
_down = -1;
_previewShown = false;
}
}
bool FieldAutocompleteInner::moveSel(int key) {
_mouseSel = false;
int32 maxSel = (_mrows->isEmpty() ? (_hrows->isEmpty() ? (_brows->isEmpty() ? _srows->size() : _brows->size()) : _hrows->size()) : _mrows->size());
int32 direction = (key == Qt::Key_Up) ? -1 : (key == Qt::Key_Down ? 1 : 0);
if (!_srows->isEmpty()) {
if (key == Qt::Key_Left) {
direction = -1;
} else if (key == Qt::Key_Right) {
direction = 1;
} else {
direction *= _stickersPerRow;
}
}
if (_sel >= maxSel || _sel < 0) {
if (direction < -1) {
setSel(((maxSel - 1) / _stickersPerRow) * _stickersPerRow, true);
} else if (direction < 0) {
setSel(maxSel - 1, true);
} else {
setSel(0, true);
}
return (_sel >= 0 && _sel < maxSel);
}
setSel((_sel + direction >= maxSel || _sel + direction < 0) ? -1 : (_sel + direction), true);
return true;
}
bool FieldAutocompleteInner::chooseSelected(FieldAutocomplete::ChooseMethod method) const {
if (!_srows->isEmpty()) {
if (_sel >= 0 && _sel < _srows->size()) {
emit stickerChosen(_srows->at(_sel), method);
return true;
}
} else if (!_mrows->isEmpty()) {
if (_sel >= 0 && _sel < _mrows->size()) {
emit mentionChosen(_mrows->at(_sel), method);
return true;
}
} else if (!_hrows->isEmpty()) {
if (_sel >= 0 && _sel < _hrows->size()) {
emit hashtagChosen('#' + _hrows->at(_sel), method);
return true;
}
} else if (!_brows->isEmpty()) {
if (_sel >= 0 && _sel < _brows->size()) {
UserData *user = _brows->at(_sel).first;
const BotCommand *command(_brows->at(_sel).second);
int32 botStatus = _parent->chat() ? _parent->chat()->botStatus : ((_parent->channel() && _parent->channel()->isMegagroup()) ? _parent->channel()->mgInfo->botStatus : -1);
if (botStatus == 0 || botStatus == 2 || _parent->filter().indexOf('@') > 0) {
emit botCommandChosen('/' + command->command + '@' + user->username, method);
} else {
emit botCommandChosen('/' + command->command, method);
}
return true;
}
}
return false;
}
void FieldAutocompleteInner::setRecentInlineBotsInRows(int32 bots) {
_recentInlineBotsInRows = bots;
}
void FieldAutocompleteInner::mousePressEvent(QMouseEvent *e) {
_mousePos = mapToGlobal(e->pos());
_mouseSel = true;
onUpdateSelected(true);
if (e->button() == Qt::LeftButton) {
if (_overDelete && _sel >= 0 && _sel < (_mrows->isEmpty() ? _hrows->size() : _recentInlineBotsInRows)) {
_mousePos = mapToGlobal(e->pos());
bool removed = false;
if (_mrows->isEmpty()) {
QString toRemove = _hrows->at(_sel);
RecentHashtagPack &recent(cRefRecentWriteHashtags());
for (RecentHashtagPack::iterator i = recent.begin(); i != recent.cend();) {
if (i->first == toRemove) {
i = recent.erase(i);
removed = true;
} else {
++i;
}
}
} else {
UserData *toRemove = _mrows->at(_sel);
RecentInlineBots &recent(cRefRecentInlineBots());
int32 index = recent.indexOf(toRemove);
if (index >= 0) {
recent.remove(index);
removed = true;
}
}
if (removed) {
Local::writeRecentHashtagsAndBots();
}
_parent->updateFiltered();
_mouseSel = true;
onUpdateSelected(true);
} else if (_srows->isEmpty()) {
chooseSelected(FieldAutocomplete::ChooseMethod::ByClick);
} else {
_down = _sel;
_previewTimer.start(QApplication::startDragTime());
}
}
}
void FieldAutocompleteInner::mouseReleaseEvent(QMouseEvent *e) {
_previewTimer.stop();
int32 pressed = _down;
_down = -1;
_mousePos = mapToGlobal(e->pos());
_mouseSel = true;
onUpdateSelected(true);
if (_previewShown) {
_previewShown = false;
return;
}
if (_sel < 0 || _sel != pressed || _srows->isEmpty()) return;
chooseSelected(FieldAutocomplete::ChooseMethod::ByClick);
}
void FieldAutocompleteInner::enterEvent(QEvent *e) {
setMouseTracking(true);
_mousePos = QCursor::pos();
onUpdateSelected(true);
}
void FieldAutocompleteInner::leaveEvent(QEvent *e) {
setMouseTracking(false);
if (_sel >= 0) {
setSel(-1);
}
}
void FieldAutocompleteInner::updateSelectedRow() {
if (_sel >= 0) {
if (_srows->isEmpty()) {
update(0, _sel * st::mentionHeight, width(), st::mentionHeight);
} else {
int32 row = _sel / _stickersPerRow, col = _sel % _stickersPerRow;
update(st::stickerPanPadding + col * st::stickerPanSize.width(), st::stickerPanPadding + row * st::stickerPanSize.height(), st::stickerPanSize.width(), st::stickerPanSize.height());
}
}
}
void FieldAutocompleteInner::setSel(int sel, bool scroll) {
updateSelectedRow();
_sel = sel;
updateSelectedRow();
if (scroll && _sel >= 0) {
if (_srows->isEmpty()) {
emit mustScrollTo(_sel * st::mentionHeight, (_sel + 1) * st::mentionHeight);
} else {
int32 row = _sel / _stickersPerRow;
emit mustScrollTo(st::stickerPanPadding + row * st::stickerPanSize.height(), st::stickerPanPadding + (row + 1) * st::stickerPanSize.height());
}
}
}
void FieldAutocompleteInner::onUpdateSelected(bool force) {
QPoint mouse(mapFromGlobal(_mousePos));
if ((!force && !rect().contains(mouse)) || !_mouseSel) return;
if (_down >= 0 && !_previewShown) return;
int32 sel = -1, maxSel = 0;
if (!_srows->isEmpty()) {
int32 rows = rowscount(_srows->size(), _stickersPerRow);
int32 row = (mouse.y() >= st::stickerPanPadding) ? ((mouse.y() - st::stickerPanPadding) / st::stickerPanSize.height()) : -1;
int32 col = (mouse.x() >= st::stickerPanPadding) ? ((mouse.x() - st::stickerPanPadding) / st::stickerPanSize.width()) : -1;
if (row >= 0 && col >= 0) {
sel = row * _stickersPerRow + col;
}
maxSel = _srows->size();
_overDelete = false;
} else {
sel = mouse.y() / int32(st::mentionHeight);
maxSel = _mrows->isEmpty() ? (_hrows->isEmpty() ? _brows->size() : _hrows->size()) : _mrows->size();
_overDelete = (!_hrows->isEmpty() || (!_mrows->isEmpty() && sel < _recentInlineBotsInRows)) ? (mouse.x() >= width() - st::mentionHeight) : false;
}
if (sel < 0 || sel >= maxSel) {
sel = -1;
}
if (sel != _sel) {
setSel(sel);
if (_down >= 0 && _sel >= 0 && _down != _sel) {
_down = _sel;
if (_down >= 0 && _down < _srows->size()) {
Ui::showMediaPreview(_srows->at(_down));
}
}
}
}
void FieldAutocompleteInner::onParentGeometryChanged() {
_mousePos = QCursor::pos();
if (rect().contains(mapFromGlobal(_mousePos))) {
setMouseTracking(true);
onUpdateSelected(true);
}
}
void FieldAutocompleteInner::onPreview() {
if (_down >= 0 && _down < _srows->size()) {
Ui::showMediaPreview(_srows->at(_down));
_previewShown = true;
}
}
} // namespace internal

View file

@ -0,0 +1,202 @@
/*
This file is part of Telegram Desktop,
the official desktop version of Telegram messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
It is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
*/
#pragma once
#include "ui/twidget.h"
#include "ui/boxshadow.h"
namespace internal {
using MentionRows = QList<UserData*>;
using HashtagRows = QList<QString>;
using BotCommandRows = QList<QPair<UserData*, const BotCommand*>>;
class FieldAutocompleteInner;
} // namespace internal
class FieldAutocomplete final : public TWidget {
Q_OBJECT
public:
FieldAutocomplete(QWidget *parent);
void fastHide();
bool clearFilteredBotCommands();
void showFiltered(PeerData *peer, QString query, bool addInlineBots);
void showStickers(EmojiPtr emoji);
void setBoundings(QRect boundings);
void step_appearance(float64 ms, bool timer);
const QString &filter() const;
ChatData *chat() const;
ChannelData *channel() const;
UserData *user() const;
int32 innerTop();
int32 innerBottom();
bool eventFilter(QObject *obj, QEvent *e) override;
enum class ChooseMethod {
ByEnter,
ByTab,
ByClick,
};
bool chooseSelected(ChooseMethod method) const;
bool stickersShown() const {
return !_srows.isEmpty();
}
bool overlaps(const QRect &globalRect) {
if (isHidden() || !testAttribute(Qt::WA_OpaquePaintEvent)) return false;
return rect().contains(QRect(mapFromGlobal(globalRect.topLeft()), globalRect.size()));
}
~FieldAutocomplete();
signals:
void mentionChosen(UserData *user, FieldAutocomplete::ChooseMethod method) const;
void hashtagChosen(QString hashtag, FieldAutocomplete::ChooseMethod method) const;
void botCommandChosen(QString command, FieldAutocomplete::ChooseMethod method) const;
void stickerChosen(DocumentData *sticker, FieldAutocomplete::ChooseMethod method) const;
public slots:
void hideStart();
void hideFinish();
void showStart();
private:
void paintEvent(QPaintEvent *e) override;
void updateFiltered(bool resetScroll = false);
void recount(bool resetScroll = false);
QPixmap _cache;
internal::MentionRows _mrows;
internal::HashtagRows _hrows;
internal::BotCommandRows _brows;
StickerPack _srows;
void rowsUpdated(const internal::MentionRows &mrows, const internal::HashtagRows &hrows, const internal::BotCommandRows &brows, const StickerPack &srows, bool resetScroll);
ChildWidget<ScrollArea> _scroll;
ChildWidget<internal::FieldAutocompleteInner> _inner;
ChatData *_chat;
UserData *_user;
ChannelData *_channel;
EmojiPtr _emoji;
enum class Type {
Mentions,
Hashtags,
BotCommands,
Stickers,
};
Type _type = Type::Mentions;
QString _filter;
QRect _boundings;
bool _addInlineBots;
int32 _width, _height;
bool _hiding;
anim::fvalue a_opacity;
Animation _a_appearance;
QTimer _hideTimer;
BoxShadow _shadow;
friend class internal::FieldAutocompleteInner;
};
namespace internal {
class FieldAutocompleteInner final : public TWidget {
Q_OBJECT
public:
FieldAutocompleteInner(FieldAutocomplete *parent, MentionRows *mrows, HashtagRows *hrows, BotCommandRows *brows, StickerPack *srows);
void clearSel(bool hidden = false);
bool moveSel(int key);
bool chooseSelected(FieldAutocomplete::ChooseMethod method) const;
void setRecentInlineBotsInRows(int32 bots);
signals:
void mentionChosen(UserData *user, FieldAutocomplete::ChooseMethod method) const;
void hashtagChosen(QString hashtag, FieldAutocomplete::ChooseMethod method) const;
void botCommandChosen(QString command, FieldAutocomplete::ChooseMethod method) const;
void stickerChosen(DocumentData *sticker, FieldAutocomplete::ChooseMethod method) const;
void mustScrollTo(int scrollToTop, int scrollToBottom);
public slots:
void onParentGeometryChanged();
void onUpdateSelected(bool force = false);
void onPreview();
private:
void paintEvent(QPaintEvent *e) override;
void resizeEvent(QResizeEvent *e) override;
void enterEvent(QEvent *e) override;
void leaveEvent(QEvent *e) override;
void mousePressEvent(QMouseEvent *e) override;
void mouseMoveEvent(QMouseEvent *e) override;
void mouseReleaseEvent(QMouseEvent *e) override;
void updateSelectedRow();
void setSel(int sel, bool scroll = false);
FieldAutocomplete *_parent;
MentionRows *_mrows;
HashtagRows *_hrows;
BotCommandRows *_brows;
StickerPack *_srows;
int32 _stickersPerRow, _recentInlineBotsInRows;
int32 _sel, _down;
bool _mouseSel;
QPoint _mousePos;
bool _overDelete;
bool _previewShown;
QTimer _previewTimer;
};
} // namespace internal

File diff suppressed because it is too large Load diff

View file

@ -24,6 +24,14 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
#include "ui/boxshadow.h"
#include "dropdown.h"
#include "history/history_common.h"
#include "history/field_autocomplete.h"
namespace InlineBots {
namespace Layout {
class ItemBase;
} // namespace Layout
class Result;
} // namespace InlineBots
class HistoryWidget;
class HistoryInner : public TWidget, public AbstractTooltipShower {
@ -49,7 +57,7 @@ public:
void keyPressEvent(QKeyEvent *e) override;
void showContextMenu(QContextMenuEvent *e, bool showFromTouch = false);
QString getSelectedText() const;
TextWithEntities getSelectedText() const;
void dragActionStart(const QPoint &screenPos, Qt::MouseButton button = Qt::LeftButton);
void dragActionUpdate(const QPoint &screenPos);
@ -136,6 +144,8 @@ private:
HistoryItem *nextItem(HistoryItem *item);
void updateDragSelection(HistoryItem *dragSelFrom, HistoryItem *dragSelTo, bool dragSelecting, bool force = false);
void setToClipboard(const TextWithEntities &forClipboard);
PeerData *_peer = nullptr;
History *_migrated = nullptr;
History *_history = nullptr;
@ -478,17 +488,8 @@ public:
};
enum TextUpdateEventsFlags {
TextUpdateEventsSaveDraft = 0x01,
TextUpdateEventsSendTyping = 0x02,
};
namespace InlineBots {
namespace Layout {
class ItemBase;
} // namespace Layout
class Result;
} // namespace InlineBots
EntitiesInText entitiesFromTextTags(const TextWithTags::Tags &tags);
TextWithTags::Tags textTagsFromEntities(const EntitiesInText &entities);
class HistoryWidget : public TWidget, public RPCSender {
Q_OBJECT
@ -547,7 +548,6 @@ public:
void destroyData();
void updateFieldPlaceholder();
void updateInlineBotQuery();
void updateStickersByEmoji();
void uploadImage(const QImage &img, PrepareMediaType type, FileLoadForceConfirmType confirm = FileLoadNoForceConfirm, const QString &source = QString(), bool withText = false);
@ -757,7 +757,6 @@ public slots:
void activate();
void onStickersUpdated();
void onMentionHashtagOrBotCommandInsert(QString str);
void onTextChange();
void onFieldTabbed();
@ -775,7 +774,7 @@ public slots:
void onFieldFocused();
void onFieldResize();
void onCheckMentionDropdown();
void onCheckFieldAutocomplete();
void onScrollTimer();
void onForwardSelected();
@ -790,7 +789,6 @@ public slots:
void onDraftSave(bool delayed = false);
void updateStickers();
void updateField();
void onRecordError();
void onRecordDone(QByteArray result, VoiceWaveform waveform, qint32 samples);
@ -804,8 +802,12 @@ public slots:
private slots:
void onHashtagOrBotCommandInsert(QString str, FieldAutocomplete::ChooseMethod method);
void onMentionInsert(UserData *user);
void onInlineBotCancel();
void updateField();
private:
// Updates position of controls around the message field,
@ -816,6 +818,12 @@ private:
void clearInlineBot();
void inlineBotChanged();
// Look in the _field for the inline bot and query string.
void updateInlineBotQuery();
// Request to show results in the emoji panel.
void applyInlineBotQuery(UserData *bot, const QString &query);
MsgId _replyToId = 0;
Text _replyToName;
int _replyToNameVersion = 0;
@ -825,6 +833,7 @@ private:
HistoryItem *_replyEditMsg = nullptr;
Text _replyEditMsgText;
mutable SingleTimer _updateEditTimeLeftDisplay;
IconedButton _fieldBarCancel;
void updateReplyEditTexts(bool force = false);
@ -852,7 +861,8 @@ private:
void sendExistingDocument(DocumentData *doc, const QString &caption);
void sendExistingPhoto(PhotoData *photo, const QString &caption);
void drawField(Painter &p);
void drawField(Painter &p, const QRect &rect);
void paintEditHeader(Painter &p, const QRect &rect, int left, int top) const;
void drawRecordButton(Painter &p);
void drawRecording(Painter &p);
void drawPinnedBar(Painter &p);
@ -948,11 +958,18 @@ private:
void savedGifsGot(const MTPmessages_SavedGifs &gifs);
bool savedGifsFailed(const RPCError &error);
enum class TextUpdateEvent {
SaveDraft = 0x01,
SendTyping = 0x02,
};
Q_DECLARE_FLAGS(TextUpdateEvents, TextUpdateEvent);
Q_DECLARE_FRIEND_OPERATORS_FOR_FLAGS(TextUpdateEvents);
void writeDrafts(HistoryDraft **msgDraft, HistoryEditDraft **editDraft);
void writeDrafts(History *history);
void setFieldText(const QString &text, int32 textUpdateEventsFlags = 0, bool clearUndoHistory = true);
void clearFieldText(int32 textUpdateEventsFlags = 0, bool clearUndoHistory = true) {
setFieldText(QString());
void setFieldText(const TextWithTags &textWithTags, TextUpdateEvents events = 0, FlatTextarea::UndoHistoryAction undoHistoryAction = FlatTextarea::ClearUndoHistory);
void clearFieldText(TextUpdateEvents events = 0, FlatTextarea::UndoHistoryAction undoHistoryAction = FlatTextarea::ClearUndoHistory) {
setFieldText(TextWithTags(), events, undoHistoryAction);
}
QStringList getMediasFromMime(const QMimeData *d);
@ -1001,7 +1018,7 @@ private:
IconedButton _toHistoryEnd;
CollapseButton _collapseComments;
MentionsDropdown _attachMention;
ChildWidget<FieldAutocomplete> _fieldAutocomplete;
UserData *_inlineBot = nullptr;
QString _inlineBotUsername;
@ -1056,7 +1073,7 @@ private:
int32 _selCount; // < 0 - text selected, focus list, not _field
TaskQueue _fileLoader;
int32 _textUpdateEventsFlags = (TextUpdateEventsSaveDraft | TextUpdateEventsSendTyping);
TextUpdateEvents _textUpdateEvents = (TextUpdateEvent::SaveDraft | TextUpdateEvent::SendTyping);
int64 _serviceImageCacheSize = 0;
QString _confirmSource;
@ -1089,3 +1106,4 @@ private:
};
Q_DECLARE_OPERATORS_FOR_FLAGS(HistoryWidget::TextUpdateEvents)

View file

@ -19,9 +19,10 @@ Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
*/
#include "stdafx.h"
#include "lang.h"
#include "langloaderplain.h"
LangString langCounted(ushort key0, ushort tag, float64 value) { // current lang dependent
int v = qFloor(value);
QString sv;
@ -45,6 +46,43 @@ LangString langCounted(ushort key0, ushort tag, float64 value) { // current lang
return lang(LangKey(key0)).tag(tag, sv);
}
#define NEW_VER_TAG lt_link
#define NEW_VER_TAG_VALUE "https://telegram.org/blog/edit"
QString langNewVersionText() {
#ifdef NEW_VER_TAG
return lng_new_version_text(NEW_VER_TAG, QString::fromUtf8(NEW_VER_TAG_VALUE));
#else // NEW_VER_TAG
return lang(lng_new_version_text);
#endif // NEW_VER_TAG
}
#ifdef NEW_VER_TAG
#define NEW_VER_KEY lng_new_version_text__tagged
#define NEW_VER_POSTFIX .tag(NEW_VER_TAG, QString::fromUtf8(NEW_VER_TAG_VALUE))
#else // NEW_VER_TAG
#define NEW_VER_KEY lng_new_version_text
#define NEW_VER_POSTFIX
#endif // NEW_VER_TAG
QString langNewVersionTextForLang(int langId) {
LangLoaderResult result;
if (langId) {
LangLoaderPlain loader(qsl(":/langs/lang_") + LanguageCodes[langId].c_str() + qsl(".strings"), LangLoaderRequest(lng_language_name, NEW_VER_KEY));
result = loader.found();
} else {
result.insert(lng_language_name, langOriginal(lng_language_name));
result.insert(NEW_VER_KEY, langOriginal(NEW_VER_KEY));
}
return result.value(lng_language_name, LanguageCodes[langId].c_str() + qsl(" language")) + qsl(":\n\n") + LangString(result.value(NEW_VER_KEY, qsl("--none--")))NEW_VER_POSTFIX;
}
#undef NEW_VER_POSTFIX
#undef NEW_VER_KEY
#undef NEW_VER_TAG_VALUE
#undef NEW_VER_TAG
const QString &LangLoader::errors() const {
if (_errors.isEmpty() && !_err.isEmpty()) {
_errors = _err.join('\n');

View file

@ -142,9 +142,8 @@ inline LangString langDateTimeFull(const QDateTime &date) {
return lng_mediaview_date_time(lt_date, langDayOfMonthFull(date.date()), lt_time, date.time().toString(cTimeFormat()));
}
inline LangString langNewVersionText() {
return lng_new_version_text(lt_link, qsl("https://telegram.org/blog/bots-2-0"));
}
QString langNewVersionText();
QString langNewVersionTextForLang(int langId);
class LangLoader {
public:

View file

@ -122,14 +122,6 @@ namespace {
return true;
}
uint32 _dateTimeSize() {
return (sizeof(qint64) + sizeof(quint32) + sizeof(qint8));
}
uint32 _bytearraySize(const QByteArray &arr) {
return sizeof(quint32) + arr.size();
}
QByteArray _settingsSalt, _passKeySalt, _passKeyEncrypted;
MTP::AuthKey _oldKey, _settingsKey, _passKey, _localKey;
@ -628,18 +620,18 @@ namespace {
size += sizeof(quint64) * 2 + sizeof(quint32) + Serialize::stringSize(i.value().name());
if (AppVersion > 9013) {
// bookmark
size += _bytearraySize(i.value().bookmark());
size += Serialize::bytearraySize(i.value().bookmark());
}
// date + size
size += _dateTimeSize() + sizeof(quint32);
size += Serialize::dateTimeSize() + sizeof(quint32);
}
//end mark
size += sizeof(quint64) * 2 + sizeof(quint32) + Serialize::stringSize(QString());
if (AppVersion > 9013) {
size += _bytearraySize(QByteArray());
size += Serialize::bytearraySize(QByteArray());
}
size += _dateTimeSize() + sizeof(quint32);
size += Serialize::dateTimeSize() + sizeof(quint32);
size += sizeof(quint32); // aliases count
for (FileLocationAliases::const_iterator i = _fileLocationAliases.cbegin(), e = _fileLocationAliases.cend(); i != e; ++i) {
@ -1530,7 +1522,7 @@ namespace {
}
uint32 size = 16 * (sizeof(quint32) + sizeof(qint32));
size += sizeof(quint32) + Serialize::stringSize(cAskDownloadPath() ? QString() : cDownloadPath()) + _bytearraySize(cAskDownloadPath() ? QByteArray() : cDownloadPathBookmark());
size += sizeof(quint32) + Serialize::stringSize(cAskDownloadPath() ? QString() : cDownloadPath()) + Serialize::bytearraySize(cAskDownloadPath() ? QByteArray() : cDownloadPathBookmark());
size += sizeof(quint32) + sizeof(qint32) + (cRecentEmojisPreload().isEmpty() ? cGetRecentEmojis().size() : cRecentEmojisPreload().size()) * (sizeof(uint64) + sizeof(ushort));
size += sizeof(quint32) + sizeof(qint32) + cEmojiVariants().size() * (sizeof(uint32) + sizeof(uint64));
size += sizeof(quint32) + sizeof(qint32) + (cRecentStickersPreload().isEmpty() ? cGetRecentStickers().size() : cRecentStickersPreload().size()) * (sizeof(uint64) + sizeof(ushort));
@ -2281,8 +2273,8 @@ namespace Local {
void writeDrafts(const PeerId &peer, const MessageDraft &msgDraft, const MessageDraft &editDraft) {
if (!_working()) return;
if (msgDraft.msgId <= 0 && msgDraft.text.isEmpty() && editDraft.msgId <= 0) {
DraftsMap::iterator i = _draftsMap.find(peer);
if (msgDraft.msgId <= 0 && msgDraft.textWithTags.text.isEmpty() && editDraft.msgId <= 0) {
auto i = _draftsMap.find(peer);
if (i != _draftsMap.cend()) {
clearKey(i.value());
_draftsMap.erase(i);
@ -2292,18 +2284,25 @@ namespace Local {
_draftsNotReadMap.remove(peer);
} else {
DraftsMap::const_iterator i = _draftsMap.constFind(peer);
auto i = _draftsMap.constFind(peer);
if (i == _draftsMap.cend()) {
i = _draftsMap.insert(peer, genKey());
_mapChanged = true;
_writeMap(WriteMapFast);
}
EncryptedDescriptor data(sizeof(quint64) + Serialize::stringSize(msgDraft.text) + 2 * sizeof(qint32) + Serialize::stringSize(editDraft.text) + 2 * sizeof(qint32));
auto msgTags = FlatTextarea::serializeTagsList(msgDraft.textWithTags.tags);
auto editTags = FlatTextarea::serializeTagsList(editDraft.textWithTags.tags);
int size = sizeof(quint64);
size += Serialize::stringSize(msgDraft.textWithTags.text) + Serialize::bytearraySize(msgTags) + 2 * sizeof(qint32);
size += Serialize::stringSize(editDraft.textWithTags.text) + Serialize::bytearraySize(editTags) + 2 * sizeof(qint32);
EncryptedDescriptor data(size);
data.stream << quint64(peer);
data.stream << msgDraft.text << QByteArray();
data.stream << msgDraft.textWithTags.text << msgTags;
data.stream << qint32(msgDraft.msgId) << qint32(msgDraft.previewCancelled ? 1 : 0);
data.stream << editDraft.text << QByteArray();
data.stream << editDraft.textWithTags.text << editTags;
data.stream << qint32(editDraft.msgId) << qint32(editDraft.previewCancelled ? 1 : 0);
FileWriteDescriptor file(i.value());
@ -2372,10 +2371,10 @@ namespace Local {
}
quint64 draftPeer = 0;
QString msgText, editText;
TextWithTags msgData, editData;
QByteArray msgTagsSerialized, editTagsSerialized;
qint32 msgReplyTo = 0, msgPreviewCancelled = 0, editMsgId = 0, editPreviewCancelled = 0;
draft.stream >> draftPeer >> msgText;
draft.stream >> draftPeer >> msgData.text;
if (draft.version >= 9048) {
draft.stream >> msgTagsSerialized;
}
@ -2384,7 +2383,7 @@ namespace Local {
if (draft.version >= 8001) {
draft.stream >> msgPreviewCancelled;
if (!draft.stream.atEnd()) {
draft.stream >> editText;
draft.stream >> editData.text;
if (draft.version >= 9048) {
draft.stream >> editTagsSerialized;
}
@ -2399,18 +2398,21 @@ namespace Local {
return;
}
msgData.tags = FlatTextarea::deserializeTagsList(msgTagsSerialized, msgData.text.size());
editData.tags = FlatTextarea::deserializeTagsList(editTagsSerialized, editData.text.size());
MessageCursor msgCursor, editCursor;
_readDraftCursors(peer, msgCursor, editCursor);
if (msgText.isEmpty() && !msgReplyTo) {
if (msgData.text.isEmpty() && !msgReplyTo) {
h->clearMsgDraft();
} else {
h->setMsgDraft(std_::make_unique<HistoryDraft>(msgText, msgReplyTo, msgCursor, msgPreviewCancelled));
h->setMsgDraft(std_::make_unique<HistoryDraft>(msgData, msgReplyTo, msgCursor, msgPreviewCancelled));
}
if (!editMsgId) {
h->clearEditDraft();
} else {
h->setEditDraft(std_::make_unique<HistoryEditDraft>(editText, editMsgId, editCursor, editPreviewCancelled));
h->setEditDraft(std_::make_unique<HistoryEditDraft>(editData, editMsgId, editCursor, editPreviewCancelled));
}
}
@ -3029,7 +3031,7 @@ namespace Local {
} else {
int32 setsCount = 0;
QByteArray hashToWrite;
quint32 size = sizeof(quint32) + _bytearraySize(hashToWrite);
quint32 size = sizeof(quint32) + Serialize::bytearraySize(hashToWrite);
for (auto i = sets.cbegin(); i != sets.cend(); ++i) {
bool notLoaded = (i->flags & MTPDstickerSet_ClientFlag::f_not_loaded);
if (notLoaded) {
@ -3692,7 +3694,7 @@ namespace Local {
}
quint32 size = sizeof(quint32);
for (SavedPeers::const_iterator i = saved.cbegin(); i != saved.cend(); ++i) {
size += _peerSize(i.key()) + _dateTimeSize();
size += _peerSize(i.key()) + Serialize::dateTimeSize();
}
EncryptedDescriptor data(size);

View file

@ -105,11 +105,15 @@ namespace Local {
int32 oldSettingsVersion();
using TextWithTags = FlatTextarea::TextWithTags;
struct MessageDraft {
MessageDraft(MsgId msgId = 0, QString text = QString(), bool previewCancelled = false) : msgId(msgId), text(text), previewCancelled(previewCancelled) {
MessageDraft(MsgId msgId = 0, TextWithTags textWithTags = TextWithTags(), bool previewCancelled = false)
: msgId(msgId)
, textWithTags(textWithTags)
, previewCancelled(previewCancelled) {
}
MsgId msgId;
QString text;
TextWithTags textWithTags;
bool previewCancelled;
};
void writeDrafts(const PeerId &peer, const MessageDraft &msgDraft, const MessageDraft &editDraft);

View file

@ -25,6 +25,10 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
#include "localstorage.h"
int main(int argc, char *argv[]) {
#ifndef Q_OS_MAC // Retina display support is working fine, others are not.
QCoreApplication::setAttribute(Qt::AA_DisableHighDpiScaling, true);
#endif // Q_OS_MAC
settingsParseArgs(argc, argv);
if (cLaunchMode() == LaunchModeFixPrevious) {
return psFixPrevious();

View file

@ -44,7 +44,6 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
#include "localstorage.h"
#include "shortcuts.h"
#include "audio.h"
#include "langloaderplain.h"
MainWidget::MainWidget(MainWindow *window) : TWidget(window)
, _a_show(animation(this, &MainWidget::step_show))
@ -161,7 +160,9 @@ bool MainWidget::onShareUrl(const PeerId &peer, const QString &url, const QStrin
return false;
}
History *h = App::history(peer);
h->setMsgDraft(std_::make_unique<HistoryDraft>(url + '\n' + text, 0, MessageCursor(url.size() + 1, url.size() + 1 + text.size(), QFIXED_MAX), false));
TextWithTags textWithTags = { url + '\n' + text, TextWithTags::Tags() };
MessageCursor cursor = { url.size() + 1, url.size() + 1 + text.size(), QFIXED_MAX };
h->setMsgDraft(std_::make_unique<HistoryDraft>(textWithTags, 0, cursor, false));
h->clearEditDraft();
bool opened = _history->peer() && (_history->peer()->id == peer);
if (opened) {
@ -179,7 +180,9 @@ bool MainWidget::onInlineSwitchChosen(const PeerId &peer, const QString &botAndQ
return false;
}
History *h = App::history(peer);
h->setMsgDraft(std_::make_unique<HistoryDraft>(botAndQuery, 0, MessageCursor(botAndQuery.size(), botAndQuery.size(), QFIXED_MAX), false));
TextWithTags textWithTags = { botAndQuery, TextWithTags::Tags() };
MessageCursor cursor = { botAndQuery.size(), botAndQuery.size(), QFIXED_MAX };
h->setMsgDraft(std_::make_unique<HistoryDraft>(textWithTags, 0, cursor, false));
h->clearEditDraft();
bool opened = _history->peer() && (_history->peer()->id == peer);
if (opened) {
@ -1065,83 +1068,61 @@ void executeParsedCommand(const QString &command) {
if (command == qsl("new_version_text")) {
App::wnd()->serviceNotification(langNewVersionText());
} else if (command == qsl("all_new_version_texts")) {
#define NEW_VER_TAG lt_link
#define NEW_VER_TAG_VALUE "https://telegram.org/blog/bots-2-0"
#ifdef NEW_VER_TAG
#define NEW_VER_KEY lng_new_version_text__tagged
#define NEW_VER_POSTFIX .tag(NEW_VER_TAG, QString::fromUtf8(NEW_VER_TAG_VALUE))
#else
#define NEW_VER_KEY lng_new_version_text
#define NEW_VER_POSTFIX
#endif
for (int i = 0; i < languageCount; ++i) {
LangLoaderResult result;
if (i) {
LangLoaderPlain loader(qsl(":/langs/lang_") + LanguageCodes[i].c_str() + qsl(".strings"), LangLoaderRequest(lng_language_name, NEW_VER_KEY));
result = loader.found();
} else {
result.insert(lng_language_name, langOriginal(lng_language_name));
result.insert(NEW_VER_KEY, langOriginal(NEW_VER_KEY));
App::wnd()->serviceNotification(langNewVersionTextForLang(i));
}
App::wnd()->serviceNotification(result.value(lng_language_name, LanguageCodes[i].c_str() + qsl(" language")) + qsl(":\n\n") + LangString(result.value(NEW_VER_KEY, qsl("--none--")))NEW_VER_POSTFIX);
}
#undef NEW_VER_POSTFIX
#undef NEW_VER_KEY
#undef NEW_VER_TAG_VALUE
#undef NEW_VER_TAG
}
}
} // namespace
void MainWidget::sendMessage(History *hist, const QString &text, MsgId replyTo, bool broadcast, bool silent, WebPageId webPageId) {
readServerHistory(hist, false);
_history->fastShowAtEnd(hist);
void MainWidget::sendMessage(const MessageToSend &message) {
auto history = message.history;
const auto &textWithTags = message.textWithTags;
if (!hist || !_history->canSendMessages(hist->peer)) {
readServerHistory(history, false);
_history->fastShowAtEnd(history);
if (!history || !_history->canSendMessages(history->peer)) {
return;
}
saveRecentHashtags(text);
saveRecentHashtags(textWithTags.text);
EntitiesInText sendingEntities, leftEntities;
QString sendingText, leftText = prepareTextWithEntities(text, leftEntities, itemTextOptions(hist, App::self()).flags);
EntitiesInText sendingEntities, leftEntities = entitiesFromTextTags(textWithTags.tags);
auto prepareFlags = itemTextOptions(history, App::self()).flags;
QString sendingText, leftText = prepareTextWithEntities(textWithTags.text, prepareFlags, &leftEntities);
QString command = parseCommandFromMessage(hist, text);
QString command = parseCommandFromMessage(history, textWithTags.text);
HistoryItem *lastMessage = nullptr;
if (replyTo < 0) replyTo = _history->replyToId();
MsgId replyTo = (message.replyTo < 0) ? _history->replyToId() : 0;
while (command.isEmpty() && textSplit(sendingText, sendingEntities, leftText, leftEntities, MaxMessageSize)) {
FullMsgId newId(peerToChannel(hist->peer->id), clientMsgId());
FullMsgId newId(peerToChannel(history->peer->id), clientMsgId());
uint64 randomId = rand_value<uint64>();
trimTextWithEntities(sendingText, sendingEntities);
trimTextWithEntities(sendingText, &sendingEntities);
App::historyRegRandom(randomId, newId);
App::historyRegSentData(randomId, hist->peer->id, sendingText);
App::historyRegSentData(randomId, history->peer->id, sendingText);
MTPstring msgText(MTP_string(sendingText));
MTPDmessage::Flags flags = newMessageFlags(hist->peer) | MTPDmessage::Flag::f_entities; // unread, out
MTPDmessage::Flags flags = newMessageFlags(history->peer) | MTPDmessage::Flag::f_entities; // unread, out
MTPmessages_SendMessage::Flags sendFlags = 0;
if (replyTo) {
flags |= MTPDmessage::Flag::f_reply_to_msg_id;
sendFlags |= MTPmessages_SendMessage::Flag::f_reply_to_msg_id;
}
MTPMessageMedia media = MTP_messageMediaEmpty();
if (webPageId == CancelledWebPageId) {
if (message.webPageId == CancelledWebPageId) {
sendFlags |= MTPmessages_SendMessage::Flag::f_no_webpage;
} else if (webPageId) {
WebPageData *page = App::webPage(webPageId);
} else if (message.webPageId) {
WebPageData *page = App::webPage(message.webPageId);
media = MTP_messageMediaWebPage(MTP_webPagePending(MTP_long(page->id), MTP_int(page->pendingTill)));
flags |= MTPDmessage::Flag::f_media;
}
bool channelPost = hist->peer->isChannel() && !hist->peer->isMegagroup() && hist->peer->asChannel()->canPublish() && (hist->peer->asChannel()->isBroadcast() || broadcast);
bool showFromName = !channelPost || hist->peer->asChannel()->addsSignature();
bool silentPost = channelPost && silent;
bool channelPost = history->peer->isChannel() && !history->peer->isMegagroup() && history->peer->asChannel()->canPublish() && (history->peer->asChannel()->isBroadcast() || message.broadcast);
bool showFromName = !channelPost || history->peer->asChannel()->addsSignature();
bool silentPost = channelPost && message.silent;
if (channelPost) {
sendFlags |= MTPmessages_SendMessage::Flag::f_broadcast;
flags |= MTPDmessage::Flag::f_views;
@ -1157,13 +1138,13 @@ void MainWidget::sendMessage(History *hist, const QString &text, MsgId replyTo,
if (!sentEntities.c_vector().v.isEmpty()) {
sendFlags |= MTPmessages_SendMessage::Flag::f_entities;
}
lastMessage = hist->addNewMessage(MTP_message(MTP_flags(flags), MTP_int(newId.msg), MTP_int(showFromName ? MTP::authedId() : 0), peerToMTP(hist->peer->id), MTPnullFwdHeader, MTPint(), MTP_int(replyTo), MTP_int(unixtime()), msgText, media, MTPnullMarkup, localEntities, MTP_int(1), MTPint()), NewMessageUnread);
hist->sendRequestId = MTP::send(MTPmessages_SendMessage(MTP_flags(sendFlags), hist->peer->input, MTP_int(replyTo), msgText, MTP_long(randomId), MTPnullMarkup, sentEntities), rpcDone(&MainWidget::sentUpdatesReceived, randomId), rpcFail(&MainWidget::sendMessageFail), 0, 0, hist->sendRequestId);
lastMessage = history->addNewMessage(MTP_message(MTP_flags(flags), MTP_int(newId.msg), MTP_int(showFromName ? MTP::authedId() : 0), peerToMTP(history->peer->id), MTPnullFwdHeader, MTPint(), MTP_int(replyTo), MTP_int(unixtime()), msgText, media, MTPnullMarkup, localEntities, MTP_int(1), MTPint()), NewMessageUnread);
history->sendRequestId = MTP::send(MTPmessages_SendMessage(MTP_flags(sendFlags), history->peer->input, MTP_int(replyTo), msgText, MTP_long(randomId), MTPnullMarkup, sentEntities), rpcDone(&MainWidget::sentUpdatesReceived, randomId), rpcFail(&MainWidget::sendMessageFail), 0, 0, history->sendRequestId);
}
hist->lastSentMsg = lastMessage;
history->lastSentMsg = lastMessage;
finishForwarding(hist, broadcast, silent);
finishForwarding(history, message.broadcast, message.silent);
executeParsedCommand(command);
}
@ -1742,7 +1723,8 @@ void MainWidget::dialogsCancelled() {
void MainWidget::serviceNotification(const QString &msg, const MTPMessageMedia &media) {
MTPDmessage::Flags flags = MTPDmessage::Flag::f_unread | MTPDmessage::Flag::f_entities | MTPDmessage::Flag::f_from_id;
QString sendingText, leftText = msg;
EntitiesInText sendingEntities, leftEntities = textParseEntities(leftText, _historyTextNoMonoOptions.flags);
EntitiesInText sendingEntities, leftEntities;
textParseEntities(leftText, _historyTextNoMonoOptions.flags, &leftEntities);
HistoryItem *item = 0;
while (textSplit(sendingText, sendingEntities, leftText, leftEntities, MaxMessageSize)) {
MTPVector<MTPMessageEntity> localEntities = linksToMTP(sendingEntities);
@ -3844,6 +3826,107 @@ void MainWidget::updateReceived(const mtpPrime *from, const mtpPrime *end) {
update();
}
namespace {
bool fwdInfoDataLoaded(const MTPMessageFwdHeader &header) {
if (header.type() != mtpc_messageFwdHeader) {
return true;
}
auto &info = header.c_messageFwdHeader();
if (info.has_channel_id()) {
if (!App::channelLoaded(peerFromChannel(info.vchannel_id))) {
return false;
}
if (info.has_from_id() && !App::user(peerFromUser(info.vfrom_id), PeerData::MinimalLoaded)) {
return false;
}
} else {
if (info.has_from_id() && !App::userLoaded(peerFromUser(info.vfrom_id))) {
return false;
}
}
return true;
}
bool mentionUsersLoaded(const MTPVector<MTPMessageEntity> &entities) {
for_const (auto &entity, entities.c_vector().v) {
auto type = entity.type();
if (type == mtpc_messageEntityMentionName) {
if (!App::userLoaded(peerFromUser(entity.c_messageEntityMentionName().vuser_id))) {
return false;
}
} else if (type == mtpc_inputMessageEntityMentionName) {
auto &inputUser = entity.c_inputMessageEntityMentionName().vuser_id;
if (inputUser.type() == mtpc_inputUser) {
if (!App::userLoaded(peerFromUser(inputUser.c_inputUser().vuser_id))) {
return false;
}
}
}
}
return true;
}
enum class DataIsLoadedResult {
NotLoaded = 0,
FromNotLoaded = 1,
MentionNotLoaded = 2,
Ok = 3,
};
DataIsLoadedResult allDataLoadedForMessage(const MTPMessage &msg) {
switch (msg.type()) {
case mtpc_message: {
const MTPDmessage &d(msg.c_message());
if (!d.is_post() && d.has_from_id()) {
if (!App::userLoaded(peerFromUser(d.vfrom_id))) {
return DataIsLoadedResult::FromNotLoaded;
}
}
if (d.has_via_bot_id()) {
if (!App::userLoaded(peerFromUser(d.vvia_bot_id))) {
return DataIsLoadedResult::NotLoaded;
}
}
if (d.has_fwd_from() && !fwdInfoDataLoaded(d.vfwd_from)) {
return DataIsLoadedResult::NotLoaded;
}
if (d.has_entities() && !mentionUsersLoaded(d.ventities)) {
return DataIsLoadedResult::MentionNotLoaded;
}
} break;
case mtpc_messageService: {
const MTPDmessageService &d(msg.c_messageService());
if (!d.is_post() && d.has_from_id()) {
if (!App::userLoaded(peerFromUser(d.vfrom_id))) {
return DataIsLoadedResult::FromNotLoaded;
}
}
switch (d.vaction.type()) {
case mtpc_messageActionChatAddUser: {
for_const(const MTPint &userId, d.vaction.c_messageActionChatAddUser().vusers.c_vector().v) {
if (!App::userLoaded(peerFromUser(userId))) {
return DataIsLoadedResult::NotLoaded;
}
}
} break;
case mtpc_messageActionChatJoinedByLink: {
if (!App::userLoaded(peerFromUser(d.vaction.c_messageActionChatJoinedByLink().vinviter_id))) {
return DataIsLoadedResult::NotLoaded;
}
} break;
case mtpc_messageActionChatDeleteUser: {
if (!App::userLoaded(peerFromUser(d.vaction.c_messageActionChatDeleteUser().vuser_id))) {
return DataIsLoadedResult::NotLoaded;
}
} break;
}
} break;
}
return DataIsLoadedResult::Ok;
}
} // namespace
void MainWidget::feedUpdates(const MTPUpdates &updates, uint64 randomId) {
switch (updates.type()) {
case mtpc_updates: {
@ -3890,21 +3973,13 @@ void MainWidget::feedUpdates(const MTPUpdates &updates, uint64 randomId) {
case mtpc_updateShortMessage: {
const auto &d(updates.c_updateShortMessage());
if (!App::userLoaded(d.vuser_id.v) || (d.has_via_bot_id() && !App::userLoaded(d.vvia_bot_id.v))) {
if (!App::userLoaded(d.vuser_id.v)
|| (d.has_via_bot_id() && !App::userLoaded(d.vvia_bot_id.v))
|| (d.has_entities() && !mentionUsersLoaded(d.ventities))
|| (d.has_fwd_from() && !fwdInfoDataLoaded(d.vfwd_from))) {
MTP_LOG(0, ("getDifference { good - getting user for updateShortMessage }%1").arg(cTestMode() ? " TESTMODE" : ""));
return getDifference();
}
if (d.has_fwd_from() && d.vfwd_from.type() == mtpc_messageFwdHeader) {
const auto &f(d.vfwd_from.c_messageFwdHeader());
if (f.has_from_id() && !App::userLoaded(f.vfrom_id.v)) {
MTP_LOG(0, ("getDifference { good - getting user for updateShortMessage }%1").arg(cTestMode() ? " TESTMODE" : ""));
return getDifference();
}
if (f.has_channel_id() && !App::channelLoaded(f.vchannel_id.v)) {
MTP_LOG(0, ("getDifference { good - getting user for updateShortMessage }%1").arg(cTestMode() ? " TESTMODE" : ""));
return getDifference();
}
}
if (!ptsUpdated(d.vpts.v, d.vpts_count.v, updates)) {
return;
}
@ -3924,22 +3999,15 @@ void MainWidget::feedUpdates(const MTPUpdates &updates, uint64 randomId) {
case mtpc_updateShortChatMessage: {
const auto &d(updates.c_updateShortChatMessage());
bool noFrom = !App::userLoaded(d.vfrom_id.v);
if (!App::chatLoaded(d.vchat_id.v) || noFrom || (d.has_via_bot_id() && !App::userLoaded(d.vvia_bot_id.v))) {
if (!App::chatLoaded(d.vchat_id.v)
|| noFrom
|| (d.has_via_bot_id() && !App::userLoaded(d.vvia_bot_id.v))
|| (d.has_entities() && !mentionUsersLoaded(d.ventities))
|| (d.has_fwd_from() && !fwdInfoDataLoaded(d.vfwd_from))) {
MTP_LOG(0, ("getDifference { good - getting user for updateShortChatMessage }%1").arg(cTestMode() ? " TESTMODE" : ""));
if (noFrom && App::api()) App::api()->requestFullPeer(App::chatLoaded(d.vchat_id.v));
return getDifference();
}
if (d.has_fwd_from() && d.vfwd_from.type() == mtpc_messageFwdHeader) {
const auto &f(d.vfwd_from.c_messageFwdHeader());
if (f.has_from_id() && !App::userLoaded(f.vfrom_id.v)) {
MTP_LOG(0, ("getDifference { good - getting user for updateShortChatMessage }%1").arg(cTestMode() ? " TESTMODE" : ""));
return getDifference();
}
if (f.has_channel_id() && !App::channelLoaded(f.vchannel_id.v)) {
MTP_LOG(0, ("getDifference { good - getting user for updateShortChatMessage }%1").arg(cTestMode() ? " TESTMODE" : ""));
return getDifference();
}
}
if (!ptsUpdated(d.vpts.v, d.vpts_count.v, updates)) {
return;
}
@ -3965,8 +4033,12 @@ void MainWidget::feedUpdates(const MTPUpdates &updates, uint64 randomId) {
feedUpdate(MTP_updateMessageID(d.vid, MTP_long(randomId))); // ignore real date
if (peerId) {
if (HistoryItem *item = App::histItemById(peerToChannel(peerId), d.vid.v)) {
item->setText(text, d.has_entities() ? entitiesFromMTP(d.ventities.c_vector().v) : EntitiesInText());
if (auto item = App::histItemById(peerToChannel(peerId), d.vid.v)) {
if (d.has_entities() && !mentionUsersLoaded(d.ventities)) {
api()->requestMessageData(item->history()->peer->asChannel(), item->id, nullptr);
}
auto entities = d.has_entities() ? entitiesFromMTP(d.ventities.c_vector().v) : EntitiesInText();
item->setText({ text, entities });
item->updateMedia(d.has_media() ? (&d.vmedia) : nullptr);
item->addToOverview(AddToOverviewNew);
}
@ -3989,73 +4061,6 @@ void MainWidget::feedUpdates(const MTPUpdates &updates, uint64 randomId) {
}
}
namespace {
enum class DataIsLoadedResult {
NotLoaded = 0,
FromNotLoaded = 1,
Ok = 2,
};
DataIsLoadedResult allDataLoadedForMessage(const MTPMessage &msg) {
switch (msg.type()) {
case mtpc_message: {
const MTPDmessage &d(msg.c_message());
if (!d.is_post() && d.has_from_id()) {
if (!App::userLoaded(peerFromUser(d.vfrom_id))) {
return DataIsLoadedResult::FromNotLoaded;
}
}
if (d.has_via_bot_id()) {
if (!App::userLoaded(peerFromUser(d.vvia_bot_id))) {
return DataIsLoadedResult::NotLoaded;
}
}
if (d.has_fwd_from() && d.vfwd_from.type() == mtpc_messageFwdHeader) {
ChannelId fromChannelId = d.vfwd_from.c_messageFwdHeader().vchannel_id.v;
if (fromChannelId) {
if (!App::channelLoaded(peerFromChannel(fromChannelId))) {
return DataIsLoadedResult::NotLoaded;
}
} else {
if (!App::userLoaded(peerFromUser(d.vfwd_from.c_messageFwdHeader().vfrom_id))) {
return DataIsLoadedResult::NotLoaded;
}
}
}
} break;
case mtpc_messageService: {
const MTPDmessageService &d(msg.c_messageService());
if (!d.is_post() && d.has_from_id()) {
if (!App::userLoaded(peerFromUser(d.vfrom_id))) {
return DataIsLoadedResult::FromNotLoaded;
}
}
switch (d.vaction.type()) {
case mtpc_messageActionChatAddUser: {
for_const(const MTPint &userId, d.vaction.c_messageActionChatAddUser().vusers.c_vector().v) {
if (!App::userLoaded(peerFromUser(userId))) {
return DataIsLoadedResult::NotLoaded;
}
}
} break;
case mtpc_messageActionChatJoinedByLink: {
if (!App::userLoaded(peerFromUser(d.vaction.c_messageActionChatJoinedByLink().vinviter_id))) {
return DataIsLoadedResult::NotLoaded;
}
} break;
case mtpc_messageActionChatDeleteUser: {
if (!App::userLoaded(peerFromUser(d.vaction.c_messageActionChatDeleteUser().vuser_id))) {
return DataIsLoadedResult::NotLoaded;
}
} break;
}
} break;
}
return DataIsLoadedResult::Ok;
}
} // namespace
void MainWidget::feedUpdate(const MTPUpdate &update) {
if (!MTP::authedId()) return;

View file

@ -285,7 +285,15 @@ public:
Dialogs::IndexedList *contactsList();
Dialogs::IndexedList *dialogsList();
void sendMessage(History *hist, const QString &text, MsgId replyTo, bool broadcast, bool silent, WebPageId webPageId = 0);
struct MessageToSend {
History *history = nullptr;
TextWithTags textWithTags;
MsgId replyTo = 0;
bool broadcast = false;
bool silent = false;
WebPageId webPageId = 0;
};
void sendMessage(const MessageToSend &message);
void saveRecentHashtags(const QString &text);
void readServerHistory(History *history, bool force = true);

View file

@ -230,6 +230,8 @@ public:
void updateUnreadCounter();
QImage iconWithCounter(int size, int count, style::color bg, bool smallIcon);
bool contentOverlapped(const QRect &globalRect);
bool contentOverlapped(QWidget *w, QPaintEvent *e) {
return contentOverlapped(QRect(w->mapToGlobal(e->rect().topLeft()), e->rect().size()));
@ -282,8 +284,6 @@ public slots:
void onLogoutSure();
void updateGlobalMenu(); // for OS X top menu
QImage iconWithCounter(int size, int count, style::color bg, bool smallIcon);
void notifyUpdateAllPhotos();
void app_activateClickHandler(ClickHandlerPtr handler, Qt::MouseButton button);

View file

@ -912,7 +912,7 @@ void MediaView::displayPhoto(PhotoData *photo, HistoryItem *item) {
_caption = Text();
if (HistoryMessage *itemMsg = item ? item->toHistoryMessage() : nullptr) {
if (HistoryPhoto *photoMsg = dynamic_cast<HistoryPhoto*>(itemMsg->getMedia())) {
_caption.setText(st::mvCaptionFont, photoMsg->getCaption(), (item->author()->isUser() && item->author()->asUser()->botInfo) ? _captionBotOptions : _captionTextOptions);
_caption.setMarkedText(st::mvCaptionFont, photoMsg->getCaption(), (item->author()->isUser() && item->author()->asUser()->botInfo) ? _captionBotOptions : _captionTextOptions);
}
}

View file

@ -414,7 +414,7 @@ upload.file#96a18d5 type:storage.FileType mtime:int bytes:bytes = upload.File;
dcOption#5d8c6cc flags:# ipv6:flags.0?true media_only:flags.1?true tcpo_only:flags.2?true id:int ip_address:string port:int = DcOption;
config#317ceef4 date:int expires:int test_mode:Bool this_dc:int dc_options:Vector<DcOption> chat_size_max:int megagroup_size_max:int forwarded_count_max:int online_update_period_ms:int offline_blur_timeout_ms:int offline_idle_timeout_ms:int online_cloud_timeout_ms:int notify_cloud_delay_ms:int notify_default_delay_ms:int chat_big_size:int push_chat_period_ms:int push_chat_limit:int saved_gifs_limit:int edit_time_limit:int disabled_features:Vector<DisabledFeature> = Config;
config#c9411388 date:int expires:int test_mode:Bool this_dc:int dc_options:Vector<DcOption> chat_size_max:int megagroup_size_max:int forwarded_count_max:int online_update_period_ms:int offline_blur_timeout_ms:int offline_idle_timeout_ms:int online_cloud_timeout_ms:int notify_cloud_delay_ms:int notify_default_delay_ms:int chat_big_size:int push_chat_period_ms:int push_chat_limit:int saved_gifs_limit:int edit_time_limit:int rating_e_decay:int disabled_features:Vector<DisabledFeature> = Config;
nearestDc#8e1a1775 country:string this_dc:int nearest_dc:int = NearestDc;
@ -587,6 +587,8 @@ messageEntityItalic#826f8b60 offset:int length:int = MessageEntity;
messageEntityCode#28a20571 offset:int length:int = MessageEntity;
messageEntityPre#73924be0 offset:int length:int language:string = MessageEntity;
messageEntityTextUrl#76a6d327 offset:int length:int url:string = MessageEntity;
messageEntityMentionName#352dca58 offset:int length:int user_id:int = MessageEntity;
inputMessageEntityMentionName#208e68c9 offset:int length:int user_id:InputUser = MessageEntity;
inputChannelEmpty#ee8c1e86 = InputChannel;
inputChannel#afeb712e channel_id:int access_hash:long = InputChannel;
@ -677,6 +679,21 @@ inputBotInlineMessageID#890c3d89 dc_id:int id:long access_hash:long = InputBotIn
inlineBotSwitchPM#3c20629f text:string start_param:string = InlineBotSwitchPM;
messages.peerDialogs#3371c354 dialogs:Vector<Dialog> messages:Vector<Message> chats:Vector<Chat> users:Vector<User> state:updates.State = messages.PeerDialogs;
topPeer#edcdc05b peer:Peer rating:double = TopPeer;
topPeerCategoryBotsPM#ab661b5b = TopPeerCategory;
topPeerCategoryBotsInline#148677e2 = TopPeerCategory;
topPeerCategoryCorrespondents#637b7ed = TopPeerCategory;
topPeerCategoryGroups#bd17a14a = TopPeerCategory;
topPeerCategoryChannels#161d9628 = TopPeerCategory;
topPeerCategoryPeers#fb834291 category:TopPeerCategory count:int peers:Vector<TopPeer> = TopPeerCategoryPeers;
contacts.topPeersNotModified#de266ef5 = contacts.TopPeers;
contacts.topPeers#70b772a8 categories:Vector<TopPeerCategoryPeers> chats:Vector<Chat> users:Vector<User> = contacts.TopPeers;
---functions---
invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X;
@ -742,6 +759,8 @@ contacts.exportCard#84e53737 = Vector<int>;
contacts.importCard#4fe196fe export_card:Vector<int> = User;
contacts.search#11f812d8 q:string limit:int = contacts.Found;
contacts.resolveUsername#f93ccba3 username:string = contacts.ResolvedPeer;
contacts.getTopPeers#d4982db5 flags:# correspondents:flags.0?true bots_pm:flags.1?true bots_inline:flags.2?true groups:flags.10?true channels:flags.15?true offset:int limit:int hash:int = contacts.TopPeers;
contacts.resetTopPeerRating#1ae373ac category:TopPeerCategory peer:InputPeer = Bool;
messages.getMessages#4222fa74 id:Vector<int> = messages.Messages;
messages.getDialogs#6b47f94d offset_date:int offset_id:int offset_peer:InputPeer limit:int = messages.Dialogs;
@ -806,6 +825,7 @@ messages.editMessage#ce91e4ca flags:# no_webpage:flags.1?true peer:InputPeer id:
messages.editInlineBotMessage#130c2c85 flags:# no_webpage:flags.1?true id:InputBotInlineMessageID message:flags.11?string reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector<MessageEntity> = Bool;
messages.getBotCallbackAnswer#a6e94f04 peer:InputPeer msg_id:int data:bytes = messages.BotCallbackAnswer;
messages.setBotCallbackAnswer#481c591a flags:# alert:flags.1?true query_id:long message:flags.0?string = Bool;
messages.getPeerDialogs#19250887 peer:Vector<InputPeer> = messages.PeerDialogs;
updates.getState#edd4882a = updates.State;
updates.getDifference#a041495 pts:int date:int qts:int = updates.Difference;
@ -859,4 +879,4 @@ channels.exportMessageLink#c846d22d channel:InputChannel id:int = ExportedMessag
channels.toggleSignatures#1f69b606 channel:InputChannel enabled:Bool = Updates;
channels.updatePinnedMessage#a72ded52 flags:# silent:flags.0?true channel:InputChannel id:int = Updates;
// LAYER 51
// LAYER 52

View file

@ -3314,7 +3314,8 @@ void _serialize_config(MTPStringLogger &to, int32 stage, int32 lev, Types &types
case 16: to.add(" push_chat_limit: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
case 17: to.add(" saved_gifs_limit: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
case 18: to.add(" edit_time_limit: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
case 19: to.add(" disabled_features: "); ++stages.back(); types.push_back(00); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
case 19: to.add(" rating_e_decay: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
case 20: to.add(" disabled_features: "); ++stages.back(); types.push_back(00); 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;
}
}
@ -4768,6 +4769,36 @@ void _serialize_messageEntityTextUrl(MTPStringLogger &to, int32 stage, int32 lev
}
}
void _serialize_messageEntityMentionName(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) {
if (stage) {
to.add(",\n").addSpaces(lev);
} else {
to.add("{ messageEntityMentionName");
to.add("\n").addSpaces(lev);
}
switch (stage) {
case 0: to.add(" offset: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
case 1: to.add(" length: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
case 2: to.add(" user_id: "); ++stages.back(); types.push_back(mtpc_int+0); 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;
}
}
void _serialize_inputMessageEntityMentionName(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) {
if (stage) {
to.add(",\n").addSpaces(lev);
} else {
to.add("{ inputMessageEntityMentionName");
to.add("\n").addSpaces(lev);
}
switch (stage) {
case 0: to.add(" offset: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
case 1: to.add(" length: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
case 2: to.add(" user_id: "); ++stages.back(); types.push_back(0); 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;
}
}
void _serialize_inputChannelEmpty(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) {
to.add("{ inputChannelEmpty }"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back();
}
@ -5622,6 +5653,91 @@ void _serialize_inlineBotSwitchPM(MTPStringLogger &to, int32 stage, int32 lev, T
}
}
void _serialize_messages_peerDialogs(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) {
if (stage) {
to.add(",\n").addSpaces(lev);
} else {
to.add("{ messages_peerDialogs");
to.add("\n").addSpaces(lev);
}
switch (stage) {
case 0: to.add(" dialogs: "); ++stages.back(); types.push_back(00); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
case 1: to.add(" messages: "); ++stages.back(); types.push_back(00); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
case 2: to.add(" chats: "); ++stages.back(); types.push_back(00); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
case 3: to.add(" users: "); ++stages.back(); types.push_back(00); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
case 4: to.add(" state: "); ++stages.back(); types.push_back(0); 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;
}
}
void _serialize_topPeer(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) {
if (stage) {
to.add(",\n").addSpaces(lev);
} else {
to.add("{ topPeer");
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(" rating: "); ++stages.back(); types.push_back(mtpc_double+0); 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;
}
}
void _serialize_topPeerCategoryBotsPM(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) {
to.add("{ topPeerCategoryBotsPM }"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back();
}
void _serialize_topPeerCategoryBotsInline(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) {
to.add("{ topPeerCategoryBotsInline }"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back();
}
void _serialize_topPeerCategoryCorrespondents(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) {
to.add("{ topPeerCategoryCorrespondents }"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back();
}
void _serialize_topPeerCategoryGroups(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) {
to.add("{ topPeerCategoryGroups }"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back();
}
void _serialize_topPeerCategoryChannels(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) {
to.add("{ topPeerCategoryChannels }"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back();
}
void _serialize_topPeerCategoryPeers(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) {
if (stage) {
to.add(",\n").addSpaces(lev);
} else {
to.add("{ topPeerCategoryPeers");
to.add("\n").addSpaces(lev);
}
switch (stage) {
case 0: to.add(" category: "); ++stages.back(); types.push_back(0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
case 1: to.add(" count: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
case 2: to.add(" peers: "); ++stages.back(); types.push_back(00); 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;
}
}
void _serialize_contacts_topPeersNotModified(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) {
to.add("{ contacts_topPeersNotModified }"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back();
}
void _serialize_contacts_topPeers(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) {
if (stage) {
to.add(",\n").addSpaces(lev);
} else {
to.add("{ contacts_topPeers");
to.add("\n").addSpaces(lev);
}
switch (stage) {
case 0: to.add(" categories: "); ++stages.back(); types.push_back(00); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
case 1: to.add(" chats: "); ++stages.back(); types.push_back(00); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
case 2: to.add(" users: "); ++stages.back(); types.push_back(00); 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;
}
}
void _serialize_req_pq(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) {
if (stage) {
to.add(",\n").addSpaces(lev);
@ -5999,6 +6115,20 @@ void _serialize_contacts_unblock(MTPStringLogger &to, int32 stage, int32 lev, Ty
}
}
void _serialize_contacts_resetTopPeerRating(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) {
if (stage) {
to.add(",\n").addSpaces(lev);
} else {
to.add("{ contacts_resetTopPeerRating");
to.add("\n").addSpaces(lev);
}
switch (stage) {
case 0: to.add(" category: "); ++stages.back(); types.push_back(0); 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;
default: to.add("}"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back(); break;
}
}
void _serialize_messages_setTyping(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) {
if (stage) {
to.add(",\n").addSpaces(lev);
@ -6826,6 +6956,29 @@ void _serialize_contacts_resolveUsername(MTPStringLogger &to, int32 stage, int32
}
}
void _serialize_contacts_getTopPeers(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) {
MTPcontacts_getTopPeers::Flags flag(iflag);
if (stage) {
to.add(",\n").addSpaces(lev);
} else {
to.add("{ contacts_getTopPeers");
to.add("\n").addSpaces(lev);
}
switch (stage) {
case 0: to.add(" flags: "); ++stages.back(); if (start >= end) throw Exception("start >= end in flags"); else flags.back() = *start; types.push_back(mtpc_flags); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
case 1: to.add(" correspondents: "); ++stages.back(); if (flag & MTPcontacts_getTopPeers::Flag::f_correspondents) { to.add("YES [ BY BIT 0 IN FIELD flags ]"); } else { to.add("[ SKIPPED BY BIT 0 IN FIELD flags ]"); } break;
case 2: to.add(" bots_pm: "); ++stages.back(); if (flag & MTPcontacts_getTopPeers::Flag::f_bots_pm) { to.add("YES [ BY BIT 1 IN FIELD flags ]"); } else { to.add("[ SKIPPED BY BIT 1 IN FIELD flags ]"); } break;
case 3: to.add(" bots_inline: "); ++stages.back(); if (flag & MTPcontacts_getTopPeers::Flag::f_bots_inline) { to.add("YES [ BY BIT 2 IN FIELD flags ]"); } else { to.add("[ SKIPPED BY BIT 2 IN FIELD flags ]"); } break;
case 4: to.add(" groups: "); ++stages.back(); if (flag & MTPcontacts_getTopPeers::Flag::f_groups) { to.add("YES [ BY BIT 10 IN FIELD flags ]"); } else { to.add("[ SKIPPED BY BIT 10 IN FIELD flags ]"); } break;
case 5: to.add(" channels: "); ++stages.back(); if (flag & MTPcontacts_getTopPeers::Flag::f_channels) { to.add("YES [ BY BIT 15 IN FIELD flags ]"); } else { to.add("[ SKIPPED BY BIT 15 IN FIELD flags ]"); } break;
case 6: to.add(" offset: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
case 7: to.add(" limit: "); ++stages.back(); types.push_back(mtpc_int+0); vtypes.push_back(0); stages.push_back(0); flags.push_back(0); break;
case 8: to.add(" hash: "); ++stages.back(); types.push_back(mtpc_int+0); 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;
}
}
void _serialize_messages_getMessages(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) {
if (stage) {
to.add(",\n").addSpaces(lev);
@ -7883,6 +8036,19 @@ void _serialize_messages_getBotCallbackAnswer(MTPStringLogger &to, int32 stage,
}
}
void _serialize_messages_getPeerDialogs(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) {
if (stage) {
to.add(",\n").addSpaces(lev);
} else {
to.add("{ messages_getPeerDialogs");
to.add("\n").addSpaces(lev);
}
switch (stage) {
case 0: to.add(" peer: "); ++stages.back(); types.push_back(00); 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;
}
}
void _serialize_updates_getState(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) {
to.add("{ updates_getState }"); types.pop_back(); vtypes.pop_back(); stages.pop_back(); flags.pop_back();
}
@ -8520,6 +8686,8 @@ namespace {
_serializers.insert(mtpc_messageEntityCode, _serialize_messageEntityCode);
_serializers.insert(mtpc_messageEntityPre, _serialize_messageEntityPre);
_serializers.insert(mtpc_messageEntityTextUrl, _serialize_messageEntityTextUrl);
_serializers.insert(mtpc_messageEntityMentionName, _serialize_messageEntityMentionName);
_serializers.insert(mtpc_inputMessageEntityMentionName, _serialize_inputMessageEntityMentionName);
_serializers.insert(mtpc_inputChannelEmpty, _serialize_inputChannelEmpty);
_serializers.insert(mtpc_inputChannel, _serialize_inputChannel);
_serializers.insert(mtpc_contacts_resolvedPeer, _serialize_contacts_resolvedPeer);
@ -8581,6 +8749,16 @@ namespace {
_serializers.insert(mtpc_messages_messageEditData, _serialize_messages_messageEditData);
_serializers.insert(mtpc_inputBotInlineMessageID, _serialize_inputBotInlineMessageID);
_serializers.insert(mtpc_inlineBotSwitchPM, _serialize_inlineBotSwitchPM);
_serializers.insert(mtpc_messages_peerDialogs, _serialize_messages_peerDialogs);
_serializers.insert(mtpc_topPeer, _serialize_topPeer);
_serializers.insert(mtpc_topPeerCategoryBotsPM, _serialize_topPeerCategoryBotsPM);
_serializers.insert(mtpc_topPeerCategoryBotsInline, _serialize_topPeerCategoryBotsInline);
_serializers.insert(mtpc_topPeerCategoryCorrespondents, _serialize_topPeerCategoryCorrespondents);
_serializers.insert(mtpc_topPeerCategoryGroups, _serialize_topPeerCategoryGroups);
_serializers.insert(mtpc_topPeerCategoryChannels, _serialize_topPeerCategoryChannels);
_serializers.insert(mtpc_topPeerCategoryPeers, _serialize_topPeerCategoryPeers);
_serializers.insert(mtpc_contacts_topPeersNotModified, _serialize_contacts_topPeersNotModified);
_serializers.insert(mtpc_contacts_topPeers, _serialize_contacts_topPeers);
_serializers.insert(mtpc_req_pq, _serialize_req_pq);
_serializers.insert(mtpc_req_DH_params, _serialize_req_DH_params);
@ -8611,6 +8789,7 @@ namespace {
_serializers.insert(mtpc_contacts_deleteContacts, _serialize_contacts_deleteContacts);
_serializers.insert(mtpc_contacts_block, _serialize_contacts_block);
_serializers.insert(mtpc_contacts_unblock, _serialize_contacts_unblock);
_serializers.insert(mtpc_contacts_resetTopPeerRating, _serialize_contacts_resetTopPeerRating);
_serializers.insert(mtpc_messages_setTyping, _serialize_messages_setTyping);
_serializers.insert(mtpc_messages_reportSpam, _serialize_messages_reportSpam);
_serializers.insert(mtpc_messages_hideReportSpam, _serialize_messages_hideReportSpam);
@ -8673,6 +8852,7 @@ namespace {
_serializers.insert(mtpc_messages_getMessagesViews, _serialize_messages_getMessagesViews);
_serializers.insert(mtpc_contacts_search, _serialize_contacts_search);
_serializers.insert(mtpc_contacts_resolveUsername, _serialize_contacts_resolveUsername);
_serializers.insert(mtpc_contacts_getTopPeers, _serialize_contacts_getTopPeers);
_serializers.insert(mtpc_messages_getMessages, _serialize_messages_getMessages);
_serializers.insert(mtpc_messages_getHistory, _serialize_messages_getHistory);
_serializers.insert(mtpc_messages_search, _serialize_messages_search);
@ -8743,6 +8923,7 @@ namespace {
_serializers.insert(mtpc_messages_getInlineBotResults, _serialize_messages_getInlineBotResults);
_serializers.insert(mtpc_messages_getMessageEditData, _serialize_messages_getMessageEditData);
_serializers.insert(mtpc_messages_getBotCallbackAnswer, _serialize_messages_getBotCallbackAnswer);
_serializers.insert(mtpc_messages_getPeerDialogs, _serialize_messages_getPeerDialogs);
_serializers.insert(mtpc_updates_getState, _serialize_updates_getState);
_serializers.insert(mtpc_updates_getDifference, _serialize_updates_getDifference);
_serializers.insert(mtpc_updates_getChannelDifference, _serialize_updates_getChannelDifference);

View file

@ -30,7 +30,7 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
namespace MTP {
namespace internal {
static constexpr mtpPrime CurrentLayer = 51;
static constexpr mtpPrime CurrentLayer = 52;
class TypeCreator;
@ -302,7 +302,7 @@ enum {
mtpc_photos_photo = 0x20212ca8,
mtpc_upload_file = 0x96a18d5,
mtpc_dcOption = 0x5d8c6cc,
mtpc_config = 0x317ceef4,
mtpc_config = 0xc9411388,
mtpc_nearestDc = 0x8e1a1775,
mtpc_help_appUpdate = 0x8987f311,
mtpc_help_noAppUpdate = 0xc45a6536,
@ -426,6 +426,8 @@ enum {
mtpc_messageEntityCode = 0x28a20571,
mtpc_messageEntityPre = 0x73924be0,
mtpc_messageEntityTextUrl = 0x76a6d327,
mtpc_messageEntityMentionName = 0x352dca58,
mtpc_inputMessageEntityMentionName = 0x208e68c9,
mtpc_inputChannelEmpty = 0xee8c1e86,
mtpc_inputChannel = 0xafeb712e,
mtpc_contacts_resolvedPeer = 0x7f077ad9,
@ -487,6 +489,16 @@ enum {
mtpc_messages_messageEditData = 0x26b5dde6,
mtpc_inputBotInlineMessageID = 0x890c3d89,
mtpc_inlineBotSwitchPM = 0x3c20629f,
mtpc_messages_peerDialogs = 0x3371c354,
mtpc_topPeer = 0xedcdc05b,
mtpc_topPeerCategoryBotsPM = 0xab661b5b,
mtpc_topPeerCategoryBotsInline = 0x148677e2,
mtpc_topPeerCategoryCorrespondents = 0x637b7ed,
mtpc_topPeerCategoryGroups = 0xbd17a14a,
mtpc_topPeerCategoryChannels = 0x161d9628,
mtpc_topPeerCategoryPeers = 0xfb834291,
mtpc_contacts_topPeersNotModified = 0xde266ef5,
mtpc_contacts_topPeers = 0x70b772a8,
mtpc_invokeAfterMsg = 0xcb9f372d,
mtpc_invokeAfterMsgs = 0x3dc4b4f0,
mtpc_initConnection = 0x69796de9,
@ -546,6 +558,8 @@ enum {
mtpc_contacts_importCard = 0x4fe196fe,
mtpc_contacts_search = 0x11f812d8,
mtpc_contacts_resolveUsername = 0xf93ccba3,
mtpc_contacts_getTopPeers = 0xd4982db5,
mtpc_contacts_resetTopPeerRating = 0x1ae373ac,
mtpc_messages_getMessages = 0x4222fa74,
mtpc_messages_getDialogs = 0x6b47f94d,
mtpc_messages_getHistory = 0xafa92846,
@ -609,6 +623,7 @@ enum {
mtpc_messages_editInlineBotMessage = 0x130c2c85,
mtpc_messages_getBotCallbackAnswer = 0xa6e94f04,
mtpc_messages_setBotCallbackAnswer = 0x481c591a,
mtpc_messages_getPeerDialogs = 0x19250887,
mtpc_updates_getState = 0xedd4882a,
mtpc_updates_getDifference = 0xa041495,
mtpc_updates_getChannelDifference = 0xbb32d7c0,
@ -1217,6 +1232,8 @@ class MTPDmessageEntityItalic;
class MTPDmessageEntityCode;
class MTPDmessageEntityPre;
class MTPDmessageEntityTextUrl;
class MTPDmessageEntityMentionName;
class MTPDinputMessageEntityMentionName;
class MTPinputChannel;
class MTPDinputChannel;
@ -1321,6 +1338,20 @@ class MTPDinputBotInlineMessageID;
class MTPinlineBotSwitchPM;
class MTPDinlineBotSwitchPM;
class MTPmessages_peerDialogs;
class MTPDmessages_peerDialogs;
class MTPtopPeer;
class MTPDtopPeer;
class MTPtopPeerCategory;
class MTPtopPeerCategoryPeers;
class MTPDtopPeerCategoryPeers;
class MTPcontacts_topPeers;
class MTPDcontacts_topPeers;
// Boxed types definitions
typedef MTPBoxed<MTPresPQ> MTPResPQ;
@ -1490,6 +1521,11 @@ typedef MTPBoxed<MTPmessages_botCallbackAnswer> MTPmessages_BotCallbackAnswer;
typedef MTPBoxed<MTPmessages_messageEditData> MTPmessages_MessageEditData;
typedef MTPBoxed<MTPinputBotInlineMessageID> MTPInputBotInlineMessageID;
typedef MTPBoxed<MTPinlineBotSwitchPM> MTPInlineBotSwitchPM;
typedef MTPBoxed<MTPmessages_peerDialogs> MTPmessages_PeerDialogs;
typedef MTPBoxed<MTPtopPeer> MTPTopPeer;
typedef MTPBoxed<MTPtopPeerCategory> MTPTopPeerCategory;
typedef MTPBoxed<MTPtopPeerCategoryPeers> MTPTopPeerCategoryPeers;
typedef MTPBoxed<MTPcontacts_topPeers> MTPcontacts_TopPeers;
// Type classes definitions
@ -8089,6 +8125,30 @@ public:
return *(const MTPDmessageEntityTextUrl*)data;
}
MTPDmessageEntityMentionName &_messageEntityMentionName() {
if (!data) throw mtpErrorUninitialized();
if (_type != mtpc_messageEntityMentionName) throw mtpErrorWrongTypeId(_type, mtpc_messageEntityMentionName);
split();
return *(MTPDmessageEntityMentionName*)data;
}
const MTPDmessageEntityMentionName &c_messageEntityMentionName() const {
if (!data) throw mtpErrorUninitialized();
if (_type != mtpc_messageEntityMentionName) throw mtpErrorWrongTypeId(_type, mtpc_messageEntityMentionName);
return *(const MTPDmessageEntityMentionName*)data;
}
MTPDinputMessageEntityMentionName &_inputMessageEntityMentionName() {
if (!data) throw mtpErrorUninitialized();
if (_type != mtpc_inputMessageEntityMentionName) throw mtpErrorWrongTypeId(_type, mtpc_inputMessageEntityMentionName);
split();
return *(MTPDinputMessageEntityMentionName*)data;
}
const MTPDinputMessageEntityMentionName &c_inputMessageEntityMentionName() const {
if (!data) throw mtpErrorUninitialized();
if (_type != mtpc_inputMessageEntityMentionName) throw mtpErrorWrongTypeId(_type, mtpc_inputMessageEntityMentionName);
return *(const MTPDinputMessageEntityMentionName*)data;
}
uint32 innerLength() const;
mtpTypeId type() const;
void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons);
@ -8109,6 +8169,8 @@ private:
explicit MTPmessageEntity(MTPDmessageEntityCode *_data);
explicit MTPmessageEntity(MTPDmessageEntityPre *_data);
explicit MTPmessageEntity(MTPDmessageEntityTextUrl *_data);
explicit MTPmessageEntity(MTPDmessageEntityMentionName *_data);
explicit MTPmessageEntity(MTPDinputMessageEntityMentionName *_data);
friend class MTP::internal::TypeCreator;
@ -9315,6 +9377,160 @@ private:
};
typedef MTPBoxed<MTPinlineBotSwitchPM> MTPInlineBotSwitchPM;
class MTPmessages_peerDialogs : private mtpDataOwner {
public:
MTPmessages_peerDialogs();
MTPmessages_peerDialogs(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_messages_peerDialogs) : mtpDataOwner(0) {
read(from, end, cons);
}
MTPDmessages_peerDialogs &_messages_peerDialogs() {
if (!data) throw mtpErrorUninitialized();
split();
return *(MTPDmessages_peerDialogs*)data;
}
const MTPDmessages_peerDialogs &c_messages_peerDialogs() const {
if (!data) throw mtpErrorUninitialized();
return *(const MTPDmessages_peerDialogs*)data;
}
uint32 innerLength() const;
mtpTypeId type() const;
void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_messages_peerDialogs);
void write(mtpBuffer &to) const;
typedef void ResponseType;
private:
explicit MTPmessages_peerDialogs(MTPDmessages_peerDialogs *_data);
friend class MTP::internal::TypeCreator;
};
typedef MTPBoxed<MTPmessages_peerDialogs> MTPmessages_PeerDialogs;
class MTPtopPeer : private mtpDataOwner {
public:
MTPtopPeer();
MTPtopPeer(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_topPeer) : mtpDataOwner(0) {
read(from, end, cons);
}
MTPDtopPeer &_topPeer() {
if (!data) throw mtpErrorUninitialized();
split();
return *(MTPDtopPeer*)data;
}
const MTPDtopPeer &c_topPeer() const {
if (!data) throw mtpErrorUninitialized();
return *(const MTPDtopPeer*)data;
}
uint32 innerLength() const;
mtpTypeId type() const;
void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_topPeer);
void write(mtpBuffer &to) const;
typedef void ResponseType;
private:
explicit MTPtopPeer(MTPDtopPeer *_data);
friend class MTP::internal::TypeCreator;
};
typedef MTPBoxed<MTPtopPeer> MTPTopPeer;
class MTPtopPeerCategory {
public:
MTPtopPeerCategory() : _type(0) {
}
MTPtopPeerCategory(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons) : _type(0) {
read(from, end, cons);
}
uint32 innerLength() const;
mtpTypeId type() const;
void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons);
void write(mtpBuffer &to) const;
typedef void ResponseType;
private:
explicit MTPtopPeerCategory(mtpTypeId type);
friend class MTP::internal::TypeCreator;
mtpTypeId _type;
};
typedef MTPBoxed<MTPtopPeerCategory> MTPTopPeerCategory;
class MTPtopPeerCategoryPeers : private mtpDataOwner {
public:
MTPtopPeerCategoryPeers();
MTPtopPeerCategoryPeers(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_topPeerCategoryPeers) : mtpDataOwner(0) {
read(from, end, cons);
}
MTPDtopPeerCategoryPeers &_topPeerCategoryPeers() {
if (!data) throw mtpErrorUninitialized();
split();
return *(MTPDtopPeerCategoryPeers*)data;
}
const MTPDtopPeerCategoryPeers &c_topPeerCategoryPeers() const {
if (!data) throw mtpErrorUninitialized();
return *(const MTPDtopPeerCategoryPeers*)data;
}
uint32 innerLength() const;
mtpTypeId type() const;
void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_topPeerCategoryPeers);
void write(mtpBuffer &to) const;
typedef void ResponseType;
private:
explicit MTPtopPeerCategoryPeers(MTPDtopPeerCategoryPeers *_data);
friend class MTP::internal::TypeCreator;
};
typedef MTPBoxed<MTPtopPeerCategoryPeers> MTPTopPeerCategoryPeers;
class MTPcontacts_topPeers : private mtpDataOwner {
public:
MTPcontacts_topPeers() : mtpDataOwner(0), _type(0) {
}
MTPcontacts_topPeers(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons) : mtpDataOwner(0), _type(0) {
read(from, end, cons);
}
MTPDcontacts_topPeers &_contacts_topPeers() {
if (!data) throw mtpErrorUninitialized();
if (_type != mtpc_contacts_topPeers) throw mtpErrorWrongTypeId(_type, mtpc_contacts_topPeers);
split();
return *(MTPDcontacts_topPeers*)data;
}
const MTPDcontacts_topPeers &c_contacts_topPeers() const {
if (!data) throw mtpErrorUninitialized();
if (_type != mtpc_contacts_topPeers) throw mtpErrorWrongTypeId(_type, mtpc_contacts_topPeers);
return *(const MTPDcontacts_topPeers*)data;
}
uint32 innerLength() const;
mtpTypeId type() const;
void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons);
void write(mtpBuffer &to) const;
typedef void ResponseType;
private:
explicit MTPcontacts_topPeers(mtpTypeId type);
explicit MTPcontacts_topPeers(MTPDcontacts_topPeers *_data);
friend class MTP::internal::TypeCreator;
mtpTypeId _type;
};
typedef MTPBoxed<MTPcontacts_topPeers> MTPcontacts_TopPeers;
// Type constructors with data
class MTPDresPQ : public mtpDataImpl<MTPDresPQ> {
@ -12042,7 +12258,7 @@ class MTPDconfig : public mtpDataImpl<MTPDconfig> {
public:
MTPDconfig() {
}
MTPDconfig(MTPint _date, MTPint _expires, MTPBool _test_mode, MTPint _this_dc, const MTPVector<MTPDcOption> &_dc_options, MTPint _chat_size_max, MTPint _megagroup_size_max, MTPint _forwarded_count_max, MTPint _online_update_period_ms, MTPint _offline_blur_timeout_ms, MTPint _offline_idle_timeout_ms, MTPint _online_cloud_timeout_ms, MTPint _notify_cloud_delay_ms, MTPint _notify_default_delay_ms, MTPint _chat_big_size, MTPint _push_chat_period_ms, MTPint _push_chat_limit, MTPint _saved_gifs_limit, MTPint _edit_time_limit, const MTPVector<MTPDisabledFeature> &_disabled_features) : vdate(_date), vexpires(_expires), vtest_mode(_test_mode), vthis_dc(_this_dc), vdc_options(_dc_options), vchat_size_max(_chat_size_max), vmegagroup_size_max(_megagroup_size_max), vforwarded_count_max(_forwarded_count_max), vonline_update_period_ms(_online_update_period_ms), voffline_blur_timeout_ms(_offline_blur_timeout_ms), voffline_idle_timeout_ms(_offline_idle_timeout_ms), vonline_cloud_timeout_ms(_online_cloud_timeout_ms), vnotify_cloud_delay_ms(_notify_cloud_delay_ms), vnotify_default_delay_ms(_notify_default_delay_ms), vchat_big_size(_chat_big_size), vpush_chat_period_ms(_push_chat_period_ms), vpush_chat_limit(_push_chat_limit), vsaved_gifs_limit(_saved_gifs_limit), vedit_time_limit(_edit_time_limit), vdisabled_features(_disabled_features) {
MTPDconfig(MTPint _date, MTPint _expires, MTPBool _test_mode, MTPint _this_dc, const MTPVector<MTPDcOption> &_dc_options, MTPint _chat_size_max, MTPint _megagroup_size_max, MTPint _forwarded_count_max, MTPint _online_update_period_ms, MTPint _offline_blur_timeout_ms, MTPint _offline_idle_timeout_ms, MTPint _online_cloud_timeout_ms, MTPint _notify_cloud_delay_ms, MTPint _notify_default_delay_ms, MTPint _chat_big_size, MTPint _push_chat_period_ms, MTPint _push_chat_limit, MTPint _saved_gifs_limit, MTPint _edit_time_limit, MTPint _rating_e_decay, const MTPVector<MTPDisabledFeature> &_disabled_features) : vdate(_date), vexpires(_expires), vtest_mode(_test_mode), vthis_dc(_this_dc), vdc_options(_dc_options), vchat_size_max(_chat_size_max), vmegagroup_size_max(_megagroup_size_max), vforwarded_count_max(_forwarded_count_max), vonline_update_period_ms(_online_update_period_ms), voffline_blur_timeout_ms(_offline_blur_timeout_ms), voffline_idle_timeout_ms(_offline_idle_timeout_ms), vonline_cloud_timeout_ms(_online_cloud_timeout_ms), vnotify_cloud_delay_ms(_notify_cloud_delay_ms), vnotify_default_delay_ms(_notify_default_delay_ms), vchat_big_size(_chat_big_size), vpush_chat_period_ms(_push_chat_period_ms), vpush_chat_limit(_push_chat_limit), vsaved_gifs_limit(_saved_gifs_limit), vedit_time_limit(_edit_time_limit), vrating_e_decay(_rating_e_decay), vdisabled_features(_disabled_features) {
}
MTPint vdate;
@ -12064,6 +12280,7 @@ public:
MTPint vpush_chat_limit;
MTPint vsaved_gifs_limit;
MTPint vedit_time_limit;
MTPint vrating_e_decay;
MTPVector<MTPDisabledFeature> vdisabled_features;
};
@ -13212,6 +13429,30 @@ public:
MTPstring vurl;
};
class MTPDmessageEntityMentionName : public mtpDataImpl<MTPDmessageEntityMentionName> {
public:
MTPDmessageEntityMentionName() {
}
MTPDmessageEntityMentionName(MTPint _offset, MTPint _length, MTPint _user_id) : voffset(_offset), vlength(_length), vuser_id(_user_id) {
}
MTPint voffset;
MTPint vlength;
MTPint vuser_id;
};
class MTPDinputMessageEntityMentionName : public mtpDataImpl<MTPDinputMessageEntityMentionName> {
public:
MTPDinputMessageEntityMentionName() {
}
MTPDinputMessageEntityMentionName(MTPint _offset, MTPint _length, const MTPInputUser &_user_id) : voffset(_offset), vlength(_length), vuser_id(_user_id) {
}
MTPint voffset;
MTPint vlength;
MTPInputUser vuser_id;
};
class MTPDinputChannel : public mtpDataImpl<MTPDinputChannel> {
public:
MTPDinputChannel() {
@ -14100,6 +14341,55 @@ public:
MTPstring vstart_param;
};
class MTPDmessages_peerDialogs : public mtpDataImpl<MTPDmessages_peerDialogs> {
public:
MTPDmessages_peerDialogs() {
}
MTPDmessages_peerDialogs(const MTPVector<MTPDialog> &_dialogs, const MTPVector<MTPMessage> &_messages, const MTPVector<MTPChat> &_chats, const MTPVector<MTPUser> &_users, const MTPupdates_State &_state) : vdialogs(_dialogs), vmessages(_messages), vchats(_chats), vusers(_users), vstate(_state) {
}
MTPVector<MTPDialog> vdialogs;
MTPVector<MTPMessage> vmessages;
MTPVector<MTPChat> vchats;
MTPVector<MTPUser> vusers;
MTPupdates_State vstate;
};
class MTPDtopPeer : public mtpDataImpl<MTPDtopPeer> {
public:
MTPDtopPeer() {
}
MTPDtopPeer(const MTPPeer &_peer, const MTPdouble &_rating) : vpeer(_peer), vrating(_rating) {
}
MTPPeer vpeer;
MTPdouble vrating;
};
class MTPDtopPeerCategoryPeers : public mtpDataImpl<MTPDtopPeerCategoryPeers> {
public:
MTPDtopPeerCategoryPeers() {
}
MTPDtopPeerCategoryPeers(const MTPTopPeerCategory &_category, MTPint _count, const MTPVector<MTPTopPeer> &_peers) : vcategory(_category), vcount(_count), vpeers(_peers) {
}
MTPTopPeerCategory vcategory;
MTPint vcount;
MTPVector<MTPTopPeer> vpeers;
};
class MTPDcontacts_topPeers : public mtpDataImpl<MTPDcontacts_topPeers> {
public:
MTPDcontacts_topPeers() {
}
MTPDcontacts_topPeers(const MTPVector<MTPTopPeerCategoryPeers> &_categories, const MTPVector<MTPChat> &_chats, const MTPVector<MTPUser> &_users) : vcategories(_categories), vchats(_chats), vusers(_users) {
}
MTPVector<MTPTopPeerCategoryPeers> vcategories;
MTPVector<MTPChat> vchats;
MTPVector<MTPUser> vusers;
};
// RPC methods
class MTPreq_pq { // RPC method 'req_pq'
@ -16905,6 +17195,117 @@ public:
}
};
class MTPcontacts_getTopPeers { // RPC method 'contacts.getTopPeers'
public:
enum class Flag : int32 {
f_correspondents = (1 << 0),
f_bots_pm = (1 << 1),
f_bots_inline = (1 << 2),
f_groups = (1 << 10),
f_channels = (1 << 15),
MAX_FIELD = (1 << 15),
};
Q_DECLARE_FLAGS(Flags, Flag);
friend inline Flags operator~(Flag v) { return QFlag(~static_cast<int32>(v)); }
bool is_correspondents() const { return vflags.v & Flag::f_correspondents; }
bool is_bots_pm() const { return vflags.v & Flag::f_bots_pm; }
bool is_bots_inline() const { return vflags.v & Flag::f_bots_inline; }
bool is_groups() const { return vflags.v & Flag::f_groups; }
bool is_channels() const { return vflags.v & Flag::f_channels; }
MTPflags<MTPcontacts_getTopPeers::Flags> vflags;
MTPint voffset;
MTPint vlimit;
MTPint vhash;
MTPcontacts_getTopPeers() {
}
MTPcontacts_getTopPeers(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_contacts_getTopPeers) {
read(from, end, cons);
}
MTPcontacts_getTopPeers(const MTPflags<MTPcontacts_getTopPeers::Flags> &_flags, MTPint _offset, MTPint _limit, MTPint _hash) : vflags(_flags), voffset(_offset), vlimit(_limit), vhash(_hash) {
}
uint32 innerLength() const {
return vflags.innerLength() + voffset.innerLength() + vlimit.innerLength() + vhash.innerLength();
}
mtpTypeId type() const {
return mtpc_contacts_getTopPeers;
}
void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_contacts_getTopPeers) {
vflags.read(from, end);
voffset.read(from, end);
vlimit.read(from, end);
vhash.read(from, end);
}
void write(mtpBuffer &to) const {
vflags.write(to);
voffset.write(to);
vlimit.write(to);
vhash.write(to);
}
typedef MTPcontacts_TopPeers ResponseType;
};
Q_DECLARE_OPERATORS_FOR_FLAGS(MTPcontacts_getTopPeers::Flags)
class MTPcontacts_GetTopPeers : public MTPBoxed<MTPcontacts_getTopPeers> {
public:
MTPcontacts_GetTopPeers() {
}
MTPcontacts_GetTopPeers(const MTPcontacts_getTopPeers &v) : MTPBoxed<MTPcontacts_getTopPeers>(v) {
}
MTPcontacts_GetTopPeers(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = 0) : MTPBoxed<MTPcontacts_getTopPeers>(from, end, cons) {
}
MTPcontacts_GetTopPeers(const MTPflags<MTPcontacts_getTopPeers::Flags> &_flags, MTPint _offset, MTPint _limit, MTPint _hash) : MTPBoxed<MTPcontacts_getTopPeers>(MTPcontacts_getTopPeers(_flags, _offset, _limit, _hash)) {
}
};
class MTPcontacts_resetTopPeerRating { // RPC method 'contacts.resetTopPeerRating'
public:
MTPTopPeerCategory vcategory;
MTPInputPeer vpeer;
MTPcontacts_resetTopPeerRating() {
}
MTPcontacts_resetTopPeerRating(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_contacts_resetTopPeerRating) {
read(from, end, cons);
}
MTPcontacts_resetTopPeerRating(const MTPTopPeerCategory &_category, const MTPInputPeer &_peer) : vcategory(_category), vpeer(_peer) {
}
uint32 innerLength() const {
return vcategory.innerLength() + vpeer.innerLength();
}
mtpTypeId type() const {
return mtpc_contacts_resetTopPeerRating;
}
void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_contacts_resetTopPeerRating) {
vcategory.read(from, end);
vpeer.read(from, end);
}
void write(mtpBuffer &to) const {
vcategory.write(to);
vpeer.write(to);
}
typedef MTPBool ResponseType;
};
class MTPcontacts_ResetTopPeerRating : public MTPBoxed<MTPcontacts_resetTopPeerRating> {
public:
MTPcontacts_ResetTopPeerRating() {
}
MTPcontacts_ResetTopPeerRating(const MTPcontacts_resetTopPeerRating &v) : MTPBoxed<MTPcontacts_resetTopPeerRating>(v) {
}
MTPcontacts_ResetTopPeerRating(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = 0) : MTPBoxed<MTPcontacts_resetTopPeerRating>(from, end, cons) {
}
MTPcontacts_ResetTopPeerRating(const MTPTopPeerCategory &_category, const MTPInputPeer &_peer) : MTPBoxed<MTPcontacts_resetTopPeerRating>(MTPcontacts_resetTopPeerRating(_category, _peer)) {
}
};
class MTPmessages_getMessages { // RPC method 'messages.getMessages'
public:
MTPVector<MTPint> vid;
@ -19861,6 +20262,45 @@ public:
}
};
class MTPmessages_getPeerDialogs { // RPC method 'messages.getPeerDialogs'
public:
MTPVector<MTPInputPeer> vpeer;
MTPmessages_getPeerDialogs() {
}
MTPmessages_getPeerDialogs(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_messages_getPeerDialogs) {
read(from, end, cons);
}
MTPmessages_getPeerDialogs(const MTPVector<MTPInputPeer> &_peer) : vpeer(_peer) {
}
uint32 innerLength() const {
return vpeer.innerLength();
}
mtpTypeId type() const {
return mtpc_messages_getPeerDialogs;
}
void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_messages_getPeerDialogs) {
vpeer.read(from, end);
}
void write(mtpBuffer &to) const {
vpeer.write(to);
}
typedef MTPmessages_PeerDialogs ResponseType;
};
class MTPmessages_GetPeerDialogs : public MTPBoxed<MTPmessages_getPeerDialogs> {
public:
MTPmessages_GetPeerDialogs() {
}
MTPmessages_GetPeerDialogs(const MTPmessages_getPeerDialogs &v) : MTPBoxed<MTPmessages_getPeerDialogs>(v) {
}
MTPmessages_GetPeerDialogs(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = 0) : MTPBoxed<MTPmessages_getPeerDialogs>(from, end, cons) {
}
MTPmessages_GetPeerDialogs(const MTPVector<MTPInputPeer> &_peer) : MTPBoxed<MTPmessages_getPeerDialogs>(MTPmessages_getPeerDialogs(_peer)) {
}
};
class MTPupdates_getState { // RPC method 'updates.getState'
public:
MTPupdates_getState() {
@ -22637,8 +23077,8 @@ public:
inline static MTPdcOption new_dcOption(const MTPflags<MTPDdcOption::Flags> &_flags, MTPint _id, const MTPstring &_ip_address, MTPint _port) {
return MTPdcOption(new MTPDdcOption(_flags, _id, _ip_address, _port));
}
inline static MTPconfig new_config(MTPint _date, MTPint _expires, MTPBool _test_mode, MTPint _this_dc, const MTPVector<MTPDcOption> &_dc_options, MTPint _chat_size_max, MTPint _megagroup_size_max, MTPint _forwarded_count_max, MTPint _online_update_period_ms, MTPint _offline_blur_timeout_ms, MTPint _offline_idle_timeout_ms, MTPint _online_cloud_timeout_ms, MTPint _notify_cloud_delay_ms, MTPint _notify_default_delay_ms, MTPint _chat_big_size, MTPint _push_chat_period_ms, MTPint _push_chat_limit, MTPint _saved_gifs_limit, MTPint _edit_time_limit, const MTPVector<MTPDisabledFeature> &_disabled_features) {
return MTPconfig(new MTPDconfig(_date, _expires, _test_mode, _this_dc, _dc_options, _chat_size_max, _megagroup_size_max, _forwarded_count_max, _online_update_period_ms, _offline_blur_timeout_ms, _offline_idle_timeout_ms, _online_cloud_timeout_ms, _notify_cloud_delay_ms, _notify_default_delay_ms, _chat_big_size, _push_chat_period_ms, _push_chat_limit, _saved_gifs_limit, _edit_time_limit, _disabled_features));
inline static MTPconfig new_config(MTPint _date, MTPint _expires, MTPBool _test_mode, MTPint _this_dc, const MTPVector<MTPDcOption> &_dc_options, MTPint _chat_size_max, MTPint _megagroup_size_max, MTPint _forwarded_count_max, MTPint _online_update_period_ms, MTPint _offline_blur_timeout_ms, MTPint _offline_idle_timeout_ms, MTPint _online_cloud_timeout_ms, MTPint _notify_cloud_delay_ms, MTPint _notify_default_delay_ms, MTPint _chat_big_size, MTPint _push_chat_period_ms, MTPint _push_chat_limit, MTPint _saved_gifs_limit, MTPint _edit_time_limit, MTPint _rating_e_decay, const MTPVector<MTPDisabledFeature> &_disabled_features) {
return MTPconfig(new MTPDconfig(_date, _expires, _test_mode, _this_dc, _dc_options, _chat_size_max, _megagroup_size_max, _forwarded_count_max, _online_update_period_ms, _offline_blur_timeout_ms, _offline_idle_timeout_ms, _online_cloud_timeout_ms, _notify_cloud_delay_ms, _notify_default_delay_ms, _chat_big_size, _push_chat_period_ms, _push_chat_limit, _saved_gifs_limit, _edit_time_limit, _rating_e_decay, _disabled_features));
}
inline static MTPnearestDc new_nearestDc(const MTPstring &_country, MTPint _this_dc, MTPint _nearest_dc) {
return MTPnearestDc(new MTPDnearestDc(_country, _this_dc, _nearest_dc));
@ -23009,6 +23449,12 @@ public:
inline static MTPmessageEntity new_messageEntityTextUrl(MTPint _offset, MTPint _length, const MTPstring &_url) {
return MTPmessageEntity(new MTPDmessageEntityTextUrl(_offset, _length, _url));
}
inline static MTPmessageEntity new_messageEntityMentionName(MTPint _offset, MTPint _length, MTPint _user_id) {
return MTPmessageEntity(new MTPDmessageEntityMentionName(_offset, _length, _user_id));
}
inline static MTPmessageEntity new_inputMessageEntityMentionName(MTPint _offset, MTPint _length, const MTPInputUser &_user_id) {
return MTPmessageEntity(new MTPDinputMessageEntityMentionName(_offset, _length, _user_id));
}
inline static MTPinputChannel new_inputChannelEmpty() {
return MTPinputChannel(mtpc_inputChannelEmpty);
}
@ -23192,6 +23638,36 @@ public:
inline static MTPinlineBotSwitchPM new_inlineBotSwitchPM(const MTPstring &_text, const MTPstring &_start_param) {
return MTPinlineBotSwitchPM(new MTPDinlineBotSwitchPM(_text, _start_param));
}
inline static MTPmessages_peerDialogs new_messages_peerDialogs(const MTPVector<MTPDialog> &_dialogs, const MTPVector<MTPMessage> &_messages, const MTPVector<MTPChat> &_chats, const MTPVector<MTPUser> &_users, const MTPupdates_State &_state) {
return MTPmessages_peerDialogs(new MTPDmessages_peerDialogs(_dialogs, _messages, _chats, _users, _state));
}
inline static MTPtopPeer new_topPeer(const MTPPeer &_peer, const MTPdouble &_rating) {
return MTPtopPeer(new MTPDtopPeer(_peer, _rating));
}
inline static MTPtopPeerCategory new_topPeerCategoryBotsPM() {
return MTPtopPeerCategory(mtpc_topPeerCategoryBotsPM);
}
inline static MTPtopPeerCategory new_topPeerCategoryBotsInline() {
return MTPtopPeerCategory(mtpc_topPeerCategoryBotsInline);
}
inline static MTPtopPeerCategory new_topPeerCategoryCorrespondents() {
return MTPtopPeerCategory(mtpc_topPeerCategoryCorrespondents);
}
inline static MTPtopPeerCategory new_topPeerCategoryGroups() {
return MTPtopPeerCategory(mtpc_topPeerCategoryGroups);
}
inline static MTPtopPeerCategory new_topPeerCategoryChannels() {
return MTPtopPeerCategory(mtpc_topPeerCategoryChannels);
}
inline static MTPtopPeerCategoryPeers new_topPeerCategoryPeers(const MTPTopPeerCategory &_category, MTPint _count, const MTPVector<MTPTopPeer> &_peers) {
return MTPtopPeerCategoryPeers(new MTPDtopPeerCategoryPeers(_category, _count, _peers));
}
inline static MTPcontacts_topPeers new_contacts_topPeersNotModified() {
return MTPcontacts_topPeers(mtpc_contacts_topPeersNotModified);
}
inline static MTPcontacts_topPeers new_contacts_topPeers(const MTPVector<MTPTopPeerCategoryPeers> &_categories, const MTPVector<MTPChat> &_chats, const MTPVector<MTPUser> &_users) {
return MTPcontacts_topPeers(new MTPDcontacts_topPeers(_categories, _chats, _users));
}
};
} // namespace internal
@ -29528,7 +30004,7 @@ inline MTPconfig::MTPconfig() : mtpDataOwner(new MTPDconfig()) {
inline uint32 MTPconfig::innerLength() const {
const MTPDconfig &v(c_config());
return v.vdate.innerLength() + v.vexpires.innerLength() + v.vtest_mode.innerLength() + v.vthis_dc.innerLength() + v.vdc_options.innerLength() + v.vchat_size_max.innerLength() + v.vmegagroup_size_max.innerLength() + v.vforwarded_count_max.innerLength() + v.vonline_update_period_ms.innerLength() + v.voffline_blur_timeout_ms.innerLength() + v.voffline_idle_timeout_ms.innerLength() + v.vonline_cloud_timeout_ms.innerLength() + v.vnotify_cloud_delay_ms.innerLength() + v.vnotify_default_delay_ms.innerLength() + v.vchat_big_size.innerLength() + v.vpush_chat_period_ms.innerLength() + v.vpush_chat_limit.innerLength() + v.vsaved_gifs_limit.innerLength() + v.vedit_time_limit.innerLength() + v.vdisabled_features.innerLength();
return v.vdate.innerLength() + v.vexpires.innerLength() + v.vtest_mode.innerLength() + v.vthis_dc.innerLength() + v.vdc_options.innerLength() + v.vchat_size_max.innerLength() + v.vmegagroup_size_max.innerLength() + v.vforwarded_count_max.innerLength() + v.vonline_update_period_ms.innerLength() + v.voffline_blur_timeout_ms.innerLength() + v.voffline_idle_timeout_ms.innerLength() + v.vonline_cloud_timeout_ms.innerLength() + v.vnotify_cloud_delay_ms.innerLength() + v.vnotify_default_delay_ms.innerLength() + v.vchat_big_size.innerLength() + v.vpush_chat_period_ms.innerLength() + v.vpush_chat_limit.innerLength() + v.vsaved_gifs_limit.innerLength() + v.vedit_time_limit.innerLength() + v.vrating_e_decay.innerLength() + v.vdisabled_features.innerLength();
}
inline mtpTypeId MTPconfig::type() const {
return mtpc_config;
@ -29557,6 +30033,7 @@ inline void MTPconfig::read(const mtpPrime *&from, const mtpPrime *end, mtpTypeI
v.vpush_chat_limit.read(from, end);
v.vsaved_gifs_limit.read(from, end);
v.vedit_time_limit.read(from, end);
v.vrating_e_decay.read(from, end);
v.vdisabled_features.read(from, end);
}
inline void MTPconfig::write(mtpBuffer &to) const {
@ -29580,12 +30057,13 @@ inline void MTPconfig::write(mtpBuffer &to) const {
v.vpush_chat_limit.write(to);
v.vsaved_gifs_limit.write(to);
v.vedit_time_limit.write(to);
v.vrating_e_decay.write(to);
v.vdisabled_features.write(to);
}
inline MTPconfig::MTPconfig(MTPDconfig *_data) : mtpDataOwner(_data) {
}
inline MTPconfig MTP_config(MTPint _date, MTPint _expires, MTPBool _test_mode, MTPint _this_dc, const MTPVector<MTPDcOption> &_dc_options, MTPint _chat_size_max, MTPint _megagroup_size_max, MTPint _forwarded_count_max, MTPint _online_update_period_ms, MTPint _offline_blur_timeout_ms, MTPint _offline_idle_timeout_ms, MTPint _online_cloud_timeout_ms, MTPint _notify_cloud_delay_ms, MTPint _notify_default_delay_ms, MTPint _chat_big_size, MTPint _push_chat_period_ms, MTPint _push_chat_limit, MTPint _saved_gifs_limit, MTPint _edit_time_limit, const MTPVector<MTPDisabledFeature> &_disabled_features) {
return MTP::internal::TypeCreator::new_config(_date, _expires, _test_mode, _this_dc, _dc_options, _chat_size_max, _megagroup_size_max, _forwarded_count_max, _online_update_period_ms, _offline_blur_timeout_ms, _offline_idle_timeout_ms, _online_cloud_timeout_ms, _notify_cloud_delay_ms, _notify_default_delay_ms, _chat_big_size, _push_chat_period_ms, _push_chat_limit, _saved_gifs_limit, _edit_time_limit, _disabled_features);
inline MTPconfig MTP_config(MTPint _date, MTPint _expires, MTPBool _test_mode, MTPint _this_dc, const MTPVector<MTPDcOption> &_dc_options, MTPint _chat_size_max, MTPint _megagroup_size_max, MTPint _forwarded_count_max, MTPint _online_update_period_ms, MTPint _offline_blur_timeout_ms, MTPint _offline_idle_timeout_ms, MTPint _online_cloud_timeout_ms, MTPint _notify_cloud_delay_ms, MTPint _notify_default_delay_ms, MTPint _chat_big_size, MTPint _push_chat_period_ms, MTPint _push_chat_limit, MTPint _saved_gifs_limit, MTPint _edit_time_limit, MTPint _rating_e_decay, const MTPVector<MTPDisabledFeature> &_disabled_features) {
return MTP::internal::TypeCreator::new_config(_date, _expires, _test_mode, _this_dc, _dc_options, _chat_size_max, _megagroup_size_max, _forwarded_count_max, _online_update_period_ms, _offline_blur_timeout_ms, _offline_idle_timeout_ms, _online_cloud_timeout_ms, _notify_cloud_delay_ms, _notify_default_delay_ms, _chat_big_size, _push_chat_period_ms, _push_chat_limit, _saved_gifs_limit, _edit_time_limit, _rating_e_decay, _disabled_features);
}
inline MTPnearestDc::MTPnearestDc() : mtpDataOwner(new MTPDnearestDc()) {
@ -32352,6 +32830,14 @@ inline uint32 MTPmessageEntity::innerLength() const {
const MTPDmessageEntityTextUrl &v(c_messageEntityTextUrl());
return v.voffset.innerLength() + v.vlength.innerLength() + v.vurl.innerLength();
}
case mtpc_messageEntityMentionName: {
const MTPDmessageEntityMentionName &v(c_messageEntityMentionName());
return v.voffset.innerLength() + v.vlength.innerLength() + v.vuser_id.innerLength();
}
case mtpc_inputMessageEntityMentionName: {
const MTPDinputMessageEntityMentionName &v(c_inputMessageEntityMentionName());
return v.voffset.innerLength() + v.vlength.innerLength() + v.vuser_id.innerLength();
}
}
return 0;
}
@ -32430,6 +32916,20 @@ inline void MTPmessageEntity::read(const mtpPrime *&from, const mtpPrime *end, m
v.vlength.read(from, end);
v.vurl.read(from, end);
} break;
case mtpc_messageEntityMentionName: _type = cons; {
if (!data) setData(new MTPDmessageEntityMentionName());
MTPDmessageEntityMentionName &v(_messageEntityMentionName());
v.voffset.read(from, end);
v.vlength.read(from, end);
v.vuser_id.read(from, end);
} break;
case mtpc_inputMessageEntityMentionName: _type = cons; {
if (!data) setData(new MTPDinputMessageEntityMentionName());
MTPDinputMessageEntityMentionName &v(_inputMessageEntityMentionName());
v.voffset.read(from, end);
v.vlength.read(from, end);
v.vuser_id.read(from, end);
} break;
default: throw mtpErrorUnexpected(cons, "MTPmessageEntity");
}
}
@ -32492,6 +32992,18 @@ inline void MTPmessageEntity::write(mtpBuffer &to) const {
v.vlength.write(to);
v.vurl.write(to);
} break;
case mtpc_messageEntityMentionName: {
const MTPDmessageEntityMentionName &v(c_messageEntityMentionName());
v.voffset.write(to);
v.vlength.write(to);
v.vuser_id.write(to);
} break;
case mtpc_inputMessageEntityMentionName: {
const MTPDinputMessageEntityMentionName &v(c_inputMessageEntityMentionName());
v.voffset.write(to);
v.vlength.write(to);
v.vuser_id.write(to);
} break;
}
}
inline MTPmessageEntity::MTPmessageEntity(mtpTypeId type) : mtpDataOwner(0), _type(type) {
@ -32507,6 +33019,8 @@ inline MTPmessageEntity::MTPmessageEntity(mtpTypeId type) : mtpDataOwner(0), _ty
case mtpc_messageEntityCode: setData(new MTPDmessageEntityCode()); break;
case mtpc_messageEntityPre: setData(new MTPDmessageEntityPre()); break;
case mtpc_messageEntityTextUrl: setData(new MTPDmessageEntityTextUrl()); break;
case mtpc_messageEntityMentionName: setData(new MTPDmessageEntityMentionName()); break;
case mtpc_inputMessageEntityMentionName: setData(new MTPDinputMessageEntityMentionName()); break;
default: throw mtpErrorBadTypeId(type, "MTPmessageEntity");
}
}
@ -32532,6 +33046,10 @@ inline MTPmessageEntity::MTPmessageEntity(MTPDmessageEntityPre *_data) : mtpData
}
inline MTPmessageEntity::MTPmessageEntity(MTPDmessageEntityTextUrl *_data) : mtpDataOwner(_data), _type(mtpc_messageEntityTextUrl) {
}
inline MTPmessageEntity::MTPmessageEntity(MTPDmessageEntityMentionName *_data) : mtpDataOwner(_data), _type(mtpc_messageEntityMentionName) {
}
inline MTPmessageEntity::MTPmessageEntity(MTPDinputMessageEntityMentionName *_data) : mtpDataOwner(_data), _type(mtpc_inputMessageEntityMentionName) {
}
inline MTPmessageEntity MTP_messageEntityUnknown(MTPint _offset, MTPint _length) {
return MTP::internal::TypeCreator::new_messageEntityUnknown(_offset, _length);
}
@ -32565,6 +33083,12 @@ inline MTPmessageEntity MTP_messageEntityPre(MTPint _offset, MTPint _length, con
inline MTPmessageEntity MTP_messageEntityTextUrl(MTPint _offset, MTPint _length, const MTPstring &_url) {
return MTP::internal::TypeCreator::new_messageEntityTextUrl(_offset, _length, _url);
}
inline MTPmessageEntity MTP_messageEntityMentionName(MTPint _offset, MTPint _length, MTPint _user_id) {
return MTP::internal::TypeCreator::new_messageEntityMentionName(_offset, _length, _user_id);
}
inline MTPmessageEntity MTP_inputMessageEntityMentionName(MTPint _offset, MTPint _length, const MTPInputUser &_user_id) {
return MTP::internal::TypeCreator::new_inputMessageEntityMentionName(_offset, _length, _user_id);
}
inline uint32 MTPinputChannel::innerLength() const {
switch (_type) {
@ -34263,6 +34787,199 @@ inline MTPinlineBotSwitchPM::MTPinlineBotSwitchPM(MTPDinlineBotSwitchPM *_data)
inline MTPinlineBotSwitchPM MTP_inlineBotSwitchPM(const MTPstring &_text, const MTPstring &_start_param) {
return MTP::internal::TypeCreator::new_inlineBotSwitchPM(_text, _start_param);
}
inline MTPmessages_peerDialogs::MTPmessages_peerDialogs() : mtpDataOwner(new MTPDmessages_peerDialogs()) {
}
inline uint32 MTPmessages_peerDialogs::innerLength() const {
const MTPDmessages_peerDialogs &v(c_messages_peerDialogs());
return v.vdialogs.innerLength() + v.vmessages.innerLength() + v.vchats.innerLength() + v.vusers.innerLength() + v.vstate.innerLength();
}
inline mtpTypeId MTPmessages_peerDialogs::type() const {
return mtpc_messages_peerDialogs;
}
inline void MTPmessages_peerDialogs::read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons) {
if (cons != mtpc_messages_peerDialogs) throw mtpErrorUnexpected(cons, "MTPmessages_peerDialogs");
if (!data) setData(new MTPDmessages_peerDialogs());
MTPDmessages_peerDialogs &v(_messages_peerDialogs());
v.vdialogs.read(from, end);
v.vmessages.read(from, end);
v.vchats.read(from, end);
v.vusers.read(from, end);
v.vstate.read(from, end);
}
inline void MTPmessages_peerDialogs::write(mtpBuffer &to) const {
const MTPDmessages_peerDialogs &v(c_messages_peerDialogs());
v.vdialogs.write(to);
v.vmessages.write(to);
v.vchats.write(to);
v.vusers.write(to);
v.vstate.write(to);
}
inline MTPmessages_peerDialogs::MTPmessages_peerDialogs(MTPDmessages_peerDialogs *_data) : mtpDataOwner(_data) {
}
inline MTPmessages_peerDialogs MTP_messages_peerDialogs(const MTPVector<MTPDialog> &_dialogs, const MTPVector<MTPMessage> &_messages, const MTPVector<MTPChat> &_chats, const MTPVector<MTPUser> &_users, const MTPupdates_State &_state) {
return MTP::internal::TypeCreator::new_messages_peerDialogs(_dialogs, _messages, _chats, _users, _state);
}
inline MTPtopPeer::MTPtopPeer() : mtpDataOwner(new MTPDtopPeer()) {
}
inline uint32 MTPtopPeer::innerLength() const {
const MTPDtopPeer &v(c_topPeer());
return v.vpeer.innerLength() + v.vrating.innerLength();
}
inline mtpTypeId MTPtopPeer::type() const {
return mtpc_topPeer;
}
inline void MTPtopPeer::read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons) {
if (cons != mtpc_topPeer) throw mtpErrorUnexpected(cons, "MTPtopPeer");
if (!data) setData(new MTPDtopPeer());
MTPDtopPeer &v(_topPeer());
v.vpeer.read(from, end);
v.vrating.read(from, end);
}
inline void MTPtopPeer::write(mtpBuffer &to) const {
const MTPDtopPeer &v(c_topPeer());
v.vpeer.write(to);
v.vrating.write(to);
}
inline MTPtopPeer::MTPtopPeer(MTPDtopPeer *_data) : mtpDataOwner(_data) {
}
inline MTPtopPeer MTP_topPeer(const MTPPeer &_peer, const MTPdouble &_rating) {
return MTP::internal::TypeCreator::new_topPeer(_peer, _rating);
}
inline uint32 MTPtopPeerCategory::innerLength() const {
return 0;
}
inline mtpTypeId MTPtopPeerCategory::type() const {
if (!_type) throw mtpErrorUninitialized();
return _type;
}
inline void MTPtopPeerCategory::read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons) {
switch (cons) {
case mtpc_topPeerCategoryBotsPM: _type = cons; break;
case mtpc_topPeerCategoryBotsInline: _type = cons; break;
case mtpc_topPeerCategoryCorrespondents: _type = cons; break;
case mtpc_topPeerCategoryGroups: _type = cons; break;
case mtpc_topPeerCategoryChannels: _type = cons; break;
default: throw mtpErrorUnexpected(cons, "MTPtopPeerCategory");
}
}
inline void MTPtopPeerCategory::write(mtpBuffer &to) const {
}
inline MTPtopPeerCategory::MTPtopPeerCategory(mtpTypeId type) : _type(type) {
switch (type) {
case mtpc_topPeerCategoryBotsPM: break;
case mtpc_topPeerCategoryBotsInline: break;
case mtpc_topPeerCategoryCorrespondents: break;
case mtpc_topPeerCategoryGroups: break;
case mtpc_topPeerCategoryChannels: break;
default: throw mtpErrorBadTypeId(type, "MTPtopPeerCategory");
}
}
inline MTPtopPeerCategory MTP_topPeerCategoryBotsPM() {
return MTP::internal::TypeCreator::new_topPeerCategoryBotsPM();
}
inline MTPtopPeerCategory MTP_topPeerCategoryBotsInline() {
return MTP::internal::TypeCreator::new_topPeerCategoryBotsInline();
}
inline MTPtopPeerCategory MTP_topPeerCategoryCorrespondents() {
return MTP::internal::TypeCreator::new_topPeerCategoryCorrespondents();
}
inline MTPtopPeerCategory MTP_topPeerCategoryGroups() {
return MTP::internal::TypeCreator::new_topPeerCategoryGroups();
}
inline MTPtopPeerCategory MTP_topPeerCategoryChannels() {
return MTP::internal::TypeCreator::new_topPeerCategoryChannels();
}
inline MTPtopPeerCategoryPeers::MTPtopPeerCategoryPeers() : mtpDataOwner(new MTPDtopPeerCategoryPeers()) {
}
inline uint32 MTPtopPeerCategoryPeers::innerLength() const {
const MTPDtopPeerCategoryPeers &v(c_topPeerCategoryPeers());
return v.vcategory.innerLength() + v.vcount.innerLength() + v.vpeers.innerLength();
}
inline mtpTypeId MTPtopPeerCategoryPeers::type() const {
return mtpc_topPeerCategoryPeers;
}
inline void MTPtopPeerCategoryPeers::read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons) {
if (cons != mtpc_topPeerCategoryPeers) throw mtpErrorUnexpected(cons, "MTPtopPeerCategoryPeers");
if (!data) setData(new MTPDtopPeerCategoryPeers());
MTPDtopPeerCategoryPeers &v(_topPeerCategoryPeers());
v.vcategory.read(from, end);
v.vcount.read(from, end);
v.vpeers.read(from, end);
}
inline void MTPtopPeerCategoryPeers::write(mtpBuffer &to) const {
const MTPDtopPeerCategoryPeers &v(c_topPeerCategoryPeers());
v.vcategory.write(to);
v.vcount.write(to);
v.vpeers.write(to);
}
inline MTPtopPeerCategoryPeers::MTPtopPeerCategoryPeers(MTPDtopPeerCategoryPeers *_data) : mtpDataOwner(_data) {
}
inline MTPtopPeerCategoryPeers MTP_topPeerCategoryPeers(const MTPTopPeerCategory &_category, MTPint _count, const MTPVector<MTPTopPeer> &_peers) {
return MTP::internal::TypeCreator::new_topPeerCategoryPeers(_category, _count, _peers);
}
inline uint32 MTPcontacts_topPeers::innerLength() const {
switch (_type) {
case mtpc_contacts_topPeers: {
const MTPDcontacts_topPeers &v(c_contacts_topPeers());
return v.vcategories.innerLength() + v.vchats.innerLength() + v.vusers.innerLength();
}
}
return 0;
}
inline mtpTypeId MTPcontacts_topPeers::type() const {
if (!_type) throw mtpErrorUninitialized();
return _type;
}
inline void MTPcontacts_topPeers::read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons) {
if (cons != _type) setData(0);
switch (cons) {
case mtpc_contacts_topPeersNotModified: _type = cons; break;
case mtpc_contacts_topPeers: _type = cons; {
if (!data) setData(new MTPDcontacts_topPeers());
MTPDcontacts_topPeers &v(_contacts_topPeers());
v.vcategories.read(from, end);
v.vchats.read(from, end);
v.vusers.read(from, end);
} break;
default: throw mtpErrorUnexpected(cons, "MTPcontacts_topPeers");
}
}
inline void MTPcontacts_topPeers::write(mtpBuffer &to) const {
switch (_type) {
case mtpc_contacts_topPeers: {
const MTPDcontacts_topPeers &v(c_contacts_topPeers());
v.vcategories.write(to);
v.vchats.write(to);
v.vusers.write(to);
} break;
}
}
inline MTPcontacts_topPeers::MTPcontacts_topPeers(mtpTypeId type) : mtpDataOwner(0), _type(type) {
switch (type) {
case mtpc_contacts_topPeersNotModified: break;
case mtpc_contacts_topPeers: setData(new MTPDcontacts_topPeers()); break;
default: throw mtpErrorBadTypeId(type, "MTPcontacts_topPeers");
}
}
inline MTPcontacts_topPeers::MTPcontacts_topPeers(MTPDcontacts_topPeers *_data) : mtpDataOwner(_data), _type(mtpc_contacts_topPeers) {
}
inline MTPcontacts_topPeers MTP_contacts_topPeersNotModified() {
return MTP::internal::TypeCreator::new_contacts_topPeersNotModified();
}
inline MTPcontacts_topPeers MTP_contacts_topPeers(const MTPVector<MTPTopPeerCategoryPeers> &_categories, const MTPVector<MTPChat> &_chats, const MTPVector<MTPUser> &_users) {
return MTP::internal::TypeCreator::new_contacts_topPeers(_categories, _chats, _users);
}
inline MTPDmessage::Flags mtpCastFlags(MTPDmessageService::Flags flags) { return MTPDmessage::Flags(QFlag(flags)); }
inline MTPDmessage::Flags mtpCastFlags(MTPflags<MTPDmessageService::Flags> flags) { return mtpCastFlags(flags.v); }
inline MTPDmessage::Flags mtpCastFlags(MTPDupdateShortMessage::Flags flags) { return MTPDmessage::Flags(QFlag(flags)); }

View file

@ -898,31 +898,40 @@ bool Document::updateStatusText() const {
Link::Link(HistoryMedia *media, HistoryItem *parent) : ItemBase(parent) {
AddComponents(Info::Bit());
QString text = _parent->originalText();
EntitiesInText entities = _parent->originalEntities();
const auto textWithEntities = _parent->originalText();
QString mainUrl;
auto text = textWithEntities.text;
auto &entities = textWithEntities.entities;
int32 from = 0, till = text.size(), lnk = entities.size();
for (int32 i = 0; i < lnk; ++i) {
if (entities[i].type != EntityInTextUrl && entities[i].type != EntityInTextCustomUrl && entities[i].type != EntityInTextEmail) {
for_const (const auto &entity, entities) {
auto type = entity.type();
if (type != EntityInTextUrl && type != EntityInTextCustomUrl && type != EntityInTextEmail) {
continue;
}
QString u = entities[i].text, t = text.mid(entities[i].offset, entities[i].length);
_links.push_back(LinkEntry(u.isEmpty() ? t : u, t));
auto customUrl = entity.data(), entityText = text.mid(entity.offset(), entity.length());
auto url = customUrl.isEmpty() ? entityText : customUrl;
if (_links.isEmpty()) {
mainUrl = url;
}
_links.push_back(LinkEntry(url, entityText));
}
while (lnk > 0 && till > from) {
--lnk;
if (entities[lnk].type != EntityInTextUrl && entities[lnk].type != EntityInTextCustomUrl && entities[lnk].type != EntityInTextEmail) {
const auto &entity = entities.at(lnk);
auto type = entity.type();
if (type != EntityInTextUrl && type != EntityInTextCustomUrl && type != EntityInTextEmail) {
++lnk;
break;
}
int32 afterLinkStart = entities[lnk].offset + entities[lnk].length;
int32 afterLinkStart = entity.offset() + entity.length();
if (till > afterLinkStart) {
if (!QRegularExpression(qsl("^[,.\\s_=+\\-;:`'\"\\(\\)\\[\\]\\{\\}<>*&^%\\$#@!\\\\/]+$")).match(text.mid(afterLinkStart, till - afterLinkStart)).hasMatch()) {
++lnk;
break;
}
}
till = entities[lnk].offset;
till = entity.offset();
}
if (!lnk) {
if (QRegularExpression(qsl("^[,.\\s\\-;:`'\"\\(\\)\\[\\]\\{\\}<>*&^%\\$#@!\\\\/]+$")).match(text.mid(from, till - from)).hasMatch()) {
@ -932,6 +941,7 @@ Link::Link(HistoryMedia *media, HistoryItem *parent) : ItemBase(parent) {
_page = (media && media->type() == MediaTypeWebPage) ? static_cast<HistoryWebPage*>(media)->webpage() : 0;
if (_page) {
mainUrl = _page->url;
if (_page->document) {
_photol.reset(new DocumentOpenClickHandler(_page->document));
} else if (_page->photo) {
@ -945,8 +955,8 @@ Link::Link(HistoryMedia *media, HistoryItem *parent) : ItemBase(parent) {
} else {
_photol = MakeShared<UrlClickHandler>(_page->url);
}
} else if (!_links.isEmpty()) {
_photol = MakeShared<UrlClickHandler>(_links.front().lnk->text());
} else if (!mainUrl.isEmpty()) {
_photol = MakeShared<UrlClickHandler>(mainUrl);
}
if (from >= till && _page) {
text = _page->description;
@ -984,8 +994,7 @@ Link::Link(HistoryMedia *media, HistoryItem *parent) : ItemBase(parent) {
if (_page) {
_title = _page->title;
}
QString url(_page ? _page->url : (_links.isEmpty() ? QString() : _links.at(0).lnk->text()));
QVector<QStringRef> parts = url.splitRef('/');
QVector<QStringRef> parts = mainUrl.splitRef('/');
if (!parts.isEmpty()) {
QStringRef domain = parts.at(0);
if (parts.size() > 2 && domain.endsWith(':') && parts.at(1).isEmpty()) { // http:// and others

View file

@ -1260,9 +1260,9 @@ void OverviewInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
if (_selectedMsgId) repaintItem(_selectedMsgId, -1);
} else if (!ignoreMousedItem && App::mousedItem() && App::mousedItem()->channelId() == itemChannel(_mousedItem) && App::mousedItem()->id == itemMsgId(_mousedItem)) {
_menu = new PopupMenu();
QString copyToClipboardContextItem = _contextMenuLnk ? _contextMenuLnk->copyToClipboardContextItem() : QString();
if (!copyToClipboardContextItem.isEmpty()) {
_menu->addAction(copyToClipboardContextItem, this, SLOT(copyContextUrl()))->setEnabled(true);
QString linkCopyToClipboardText = _contextMenuLnk ? _contextMenuLnk->copyToClipboardContextItemText() : QString();
if (!linkCopyToClipboardText.isEmpty()) {
_menu->addAction(linkCopyToClipboardText, this, SLOT(copyContextUrl()))->setEnabled(true);
}
_menu->addAction(lang(lng_context_to_msg), this, SLOT(goToMessage()))->setEnabled(true);
if (isUponSelected > 1) {

View file

@ -1029,15 +1029,6 @@ void ProfileInner::paintEvent(QPaintEvent *e) {
if (!data) {
data = _participantsData[cnt] = new ParticipantData();
data->name.setText(st::profileListNameFont, user->name, _textNameOptions);
if (user->botInfo) {
if (user->botInfo->readsAllHistory) {
data->online = lang(lng_status_bot_reads_all);
} else {
data->online = lang(lng_status_bot_not_reads_all);
}
} else {
data->online = App::onlineText(user, l_time);
}
if (_peerChat) {
data->admin = (peerFromUser(_peerChat->creator) == user->id) || (_peerChat->adminsEnabled() && (_peerChat->admins.constFind(user) != _peerChat->admins.cend()));
} else if (_peerChannel) {
@ -1045,6 +1036,15 @@ void ProfileInner::paintEvent(QPaintEvent *e) {
} else {
data->admin = false;
}
if (user->botInfo) {
if (user->botInfo->readsAllHistory || data->admin) {
data->online = lang(lng_status_bot_reads_all);
} else {
data->online = lang(lng_status_bot_not_reads_all);
}
} else {
data->online = App::onlineText(user, l_time);
}
if (_amCreator) {
data->cankick = (user != App::self());
} else if (_peerChat && _peerChat->amAdmin()) {

View file

@ -84,7 +84,13 @@ void MacPrivate::notifyClicked(unsigned long long peer, int msgid) {
void MacPrivate::notifyReplied(unsigned long long peer, int msgid, const char *str) {
History *history = App::history(PeerId(peer));
App::main()->sendMessage(history, QString::fromUtf8(str), (msgid > 0 && !history->peer->isUser()) ? msgid : 0, false, false);
MainWidget::MessageToSend message;
message.history = history;
message.textWithTags = { QString::fromUtf8(str), TextWithTags::Tags() };
message.replyTo = (msgid > 0 && !history->peer->isUser()) ? msgid : 0;
message.broadcast = false;
message.silent = false;
App::main()->sendMessage(message);
}
PsMainWindow::PsMainWindow(QWidget *parent) : QMainWindow(parent),
@ -425,7 +431,7 @@ void PsMainWindow::psMacUpdateMenu() {
canPaste = !Application::clipboard()->text().isEmpty();
} else if (FlatTextarea *edit = qobject_cast<FlatTextarea*>(focused)) {
canCut = canCopy = canDelete = edit->textCursor().hasSelection();
canSelectAll = !edit->getLastText().isEmpty();
canSelectAll = !edit->isEmpty();
canUndo = edit->isUndoAvailable();
canRedo = edit->isRedoAvailable();
canPaste = !Application::clipboard()->text().isEmpty();

View file

@ -23,10 +23,6 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
namespace Serialize {
int stringSize(const QString &str) {
return sizeof(quint32) + str.size() * sizeof(ushort);
}
void writeStorageImageLocation(QDataStream &stream, const StorageImageLocation &loc) {
stream << qint32(loc.width()) << qint32(loc.height());
stream << qint32(loc.dc()) << quint64(loc.volume()) << qint32(loc.local()) << quint64(loc.secret());

View file

@ -24,7 +24,17 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
namespace Serialize {
int stringSize(const QString &str);
inline int stringSize(const QString &str) {
return sizeof(quint32) + str.size() * sizeof(ushort);
}
inline int bytearraySize(const QByteArray &arr) {
return sizeof(quint32) + arr.size();
}
inline int dateTimeSize() {
return (sizeof(qint64) + sizeof(quint32) + sizeof(qint8));
}
void writeStorageImageLocation(QDataStream &stream, const StorageImageLocation &loc);
StorageImageLocation readStorageImageLocation(QDataStream &stream);

View file

@ -1512,3 +1512,18 @@ MsgId clientMsgId() {
Q_ASSERT(currentClientMsgId < EndClientMsgId);
return currentClientMsgId++;
}
QString LocationClickHandler::copyToClipboardContextItemText() const {
return lang(lng_context_copy_link);
}
void LocationClickHandler::onClick(Qt::MouseButton button) const {
if (!psLaunchMaps(_coords)) {
QDesktopServices::openUrl(_text);
}
}
void LocationClickHandler::setup() {
QString latlon(qsl("%1,%2").arg(_coords.lat).arg(_coords.lon));
_text = qsl("https://maps.google.com/maps?q=") + latlon + qsl("&ll=") + latlon + qsl("&z=16");
}

View file

@ -1348,22 +1348,23 @@ public:
LocationClickHandler(const LocationCoords &coords) : _coords(coords) {
setup();
}
QString copyToClipboardContextItem() const override;
void onClick(Qt::MouseButton button) const override;
QString tooltip() const override {
return QString();
}
QString dragText() const override {
return _text;
}
void copyToClipboard() const override {
if (!_text.isEmpty()) {
QApplication::clipboard()->setText(_text);
}
}
QString tooltip() const override {
return QString();
}
QString text() const override {
return _text;
}
void onClick(Qt::MouseButton button) const override;
QString copyToClipboardContextItemText() const override;
private:

View file

@ -83,52 +83,52 @@ inline EmojiPtr emojiFromUrl(const QString &url) {
return emojiFromKey(url.midRef(10).toULongLong(0, 16)); // skip emoji://e.
}
inline EmojiPtr emojiFromText(const QChar *ch, const QChar *e, int *plen = 0) {
EmojiPtr emoji = 0;
if (ch + 1 < e && ((ch->isHighSurrogate() && (ch + 1)->isLowSurrogate()) || (((ch->unicode() >= 0x30 && ch->unicode() < 0x3A) || ch->unicode() == 0x23 || ch->unicode() == 0x2A) && (ch + 1)->unicode() == 0x20E3))) {
inline EmojiPtr emojiFromText(const QChar *ch, const QChar *end, int *outLength = nullptr) {
EmojiPtr emoji = nullptr;
if (ch + 1 < end && ((ch->isHighSurrogate() && (ch + 1)->isLowSurrogate()) || (((ch->unicode() >= 0x30 && ch->unicode() < 0x3A) || ch->unicode() == 0x23 || ch->unicode() == 0x2A) && (ch + 1)->unicode() == 0x20E3))) {
uint32 code = (ch->unicode() << 16) | (ch + 1)->unicode();
emoji = emojiGet(code);
if (emoji) {
if (emoji == TwoSymbolEmoji) { // check two symbol
if (ch + 3 >= e) {
if (ch + 3 >= end) {
emoji = 0;
} else {
uint32 code2 = ((uint32((ch + 2)->unicode()) << 16) | uint32((ch + 3)->unicode()));
emoji = emojiGet(code, code2);
}
} else {
if (ch + 2 < e && (ch + 2)->unicode() == 0x200D) { // check sequence
EmojiPtr seq = emojiGet(ch, e);
if (ch + 2 < end && (ch + 2)->unicode() == 0x200D) { // check sequence
EmojiPtr seq = emojiGet(ch, end);
if (seq) {
emoji = seq;
}
}
}
}
} else if (ch + 2 < e && ((ch->unicode() >= 0x30 && ch->unicode() < 0x3A) || ch->unicode() == 0x23 || ch->unicode() == 0x2A) && (ch + 1)->unicode() == 0xFE0F && (ch + 2)->unicode() == 0x20E3) {
} else if (ch + 2 < end && ((ch->unicode() >= 0x30 && ch->unicode() < 0x3A) || ch->unicode() == 0x23 || ch->unicode() == 0x2A) && (ch + 1)->unicode() == 0xFE0F && (ch + 2)->unicode() == 0x20E3) {
uint32 code = (ch->unicode() << 16) | (ch + 2)->unicode();
emoji = emojiGet(code);
if (plen) *plen = emoji->len + 1;
if (outLength) *outLength = emoji->len + 1;
return emoji;
} else if (ch < e) {
} else if (ch < end) {
emoji = emojiGet(ch->unicode());
t_assert(emoji != TwoSymbolEmoji);
}
if (emoji) {
int32 len = emoji->len + ((ch + emoji->len < e && (ch + emoji->len)->unicode() == 0xFE0F) ? 1 : 0);
if (emoji->color && (ch + len + 1 < e && (ch + len)->isHighSurrogate() && (ch + len + 1)->isLowSurrogate())) { // color
int32 len = emoji->len + ((ch + emoji->len < end && (ch + emoji->len)->unicode() == 0xFE0F) ? 1 : 0);
if (emoji->color && (ch + len + 1 < end && (ch + len)->isHighSurrogate() && (ch + len + 1)->isLowSurrogate())) { // color
uint32 color = ((uint32((ch + len)->unicode()) << 16) | uint32((ch + len + 1)->unicode()));
EmojiPtr col = emojiGet(emoji, color);
if (col && col != emoji) {
len += col->len - emoji->len;
emoji = col;
if (ch + len < e && (ch + len)->unicode() == 0xFE0F) {
if (ch + len < end && (ch + len)->unicode() == 0xFE0F) {
++len;
}
}
}
if (plen) *plen = len;
if (outLength) *outLength = len;
}
return emoji;
@ -165,26 +165,25 @@ inline bool emojiEdge(const QChar *ch) {
return false;
}
inline void appendPartToResult(QString &result, const QChar *start, const QChar *from, const QChar *to, EntitiesInText &entities) {
inline void appendPartToResult(QString &result, const QChar *start, const QChar *from, const QChar *to, EntitiesInText *inOutEntities) {
if (to > from) {
for (EntitiesInText::iterator i = entities.begin(), e = entities.end(); i != e; ++i) {
if (i->offset >= to - start) break;
if (i->offset + i->length < from - start) continue;
if (i->offset >= from - start) {
i->offset -= (from - start - result.size());
i->length += (from - start - result.size());
for (auto &entity : *inOutEntities) {
if (entity.offset() >= to - start) break;
if (entity.offset() + entity.length() < from - start) continue;
if (entity.offset() >= from - start) {
entity.extendToLeft(from - start - result.size());
}
if (i->offset + i->length < to - start) {
i->length -= (from - start - result.size());
if (entity.offset() + entity.length() < to - start) {
entity.shrinkFromRight(from - start - result.size());
}
}
result.append(from, to - from);
}
}
inline QString replaceEmojis(const QString &text, EntitiesInText &entities) {
inline QString replaceEmojis(const QString &text, EntitiesInText *inOutEntities) {
QString result;
int32 currentEntity = 0, entitiesCount = entities.size();
auto currentEntity = inOutEntities->begin(), entitiesEnd = inOutEntities->end();
const QChar *emojiStart = text.constData(), *emojiEnd = emojiStart, *e = text.constData() + text.size();
bool canFindEmoji = true;
for (const QChar *ch = emojiEnd; ch != e;) {
@ -194,18 +193,18 @@ inline QString replaceEmojis(const QString &text, EntitiesInText &entities) {
emojiFind(ch, e, newEmojiEnd, emojiCode);
}
while (currentEntity < entitiesCount && ch >= emojiStart + entities[currentEntity].offset + entities[currentEntity].length) {
while (currentEntity != entitiesEnd && ch >= emojiStart + currentEntity->offset() + currentEntity->length()) {
++currentEntity;
}
EmojiPtr emoji = emojiCode ? emojiGet(emojiCode) : 0;
if (emoji && emoji != TwoSymbolEmoji &&
(ch == emojiStart || !ch->isLetterOrNumber() || !(ch - 1)->isLetterOrNumber()) &&
(newEmojiEnd == e || !newEmojiEnd->isLetterOrNumber() || newEmojiEnd == emojiStart || !(newEmojiEnd - 1)->isLetterOrNumber()) &&
(currentEntity >= entitiesCount || (ch < emojiStart + entities[currentEntity].offset && newEmojiEnd <= emojiStart + entities[currentEntity].offset) || (ch >= emojiStart + entities[currentEntity].offset + entities[currentEntity].length && newEmojiEnd > emojiStart + entities[currentEntity].offset + entities[currentEntity].length))
(currentEntity == entitiesEnd || (ch < emojiStart + currentEntity->offset() && newEmojiEnd <= emojiStart + currentEntity->offset()) || (ch >= emojiStart + currentEntity->offset() + currentEntity->length() && newEmojiEnd > emojiStart + currentEntity->offset() + currentEntity->length()))
) {
if (result.isEmpty()) result.reserve(text.size());
appendPartToResult(result, emojiStart, emojiEnd, ch, entities);
appendPartToResult(result, emojiStart, emojiEnd, ch, inOutEntities);
if (emoji->color) {
EmojiColorVariants::const_iterator it = cEmojiVariants().constFind(emoji->code);
@ -233,7 +232,7 @@ inline QString replaceEmojis(const QString &text, EntitiesInText &entities) {
}
if (result.isEmpty()) return text;
appendPartToResult(result, emojiStart, emojiEnd, e, entities);
appendPartToResult(result, emojiStart, emojiEnd, e, inOutEntities);
return result;
}

View file

@ -1011,7 +1011,8 @@ void InputArea::processDocumentContentsChange(int position, int charsAdded) {
if (!_inner.document()->pageSize().isNull()) {
_inner.document()->setPageSize(QSizeF(0, 0));
}
QTextCursor c(doc->docHandle(), replacePosition);
QTextCursor c(doc->docHandle(), 0);
c.setPosition(replacePosition);
c.setPosition(replacePosition + replaceLen, QTextCursor::KeepAnchor);
if (emoji) {
insertEmoji(emoji, c);

View file

@ -23,13 +23,72 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
#include "mainwindow.h"
FlatTextarea::FlatTextarea(QWidget *parent, const style::flatTextarea &st, const QString &pholder, const QString &v) : QTextEdit(parent)
, _oldtext(v)
QByteArray FlatTextarea::serializeTagsList(const TagList &tags) {
if (tags.isEmpty()) {
return QByteArray();
}
QByteArray tagsSerialized;
{
QBuffer buffer(&tagsSerialized);
buffer.open(QIODevice::WriteOnly);
QDataStream stream(&buffer);
stream.setVersion(QDataStream::Qt_5_1);
stream << qint32(tags.size());
for_const (auto &tag, tags) {
stream << qint32(tag.offset) << qint32(tag.length) << tag.id;
}
}
return tagsSerialized;
}
FlatTextarea::TagList FlatTextarea::deserializeTagsList(QByteArray data, int textLength) {
TagList result;
if (data.isEmpty()) {
return result;
}
QBuffer buffer(&data);
buffer.open(QIODevice::ReadOnly);
QDataStream stream(&buffer);
stream.setVersion(QDataStream::Qt_5_1);
qint32 tagCount = 0;
stream >> tagCount;
if (stream.status() != QDataStream::Ok) {
return result;
}
if (tagCount <= 0 || tagCount > textLength) {
return result;
}
for (int i = 0; i < tagCount; ++i) {
qint32 offset = 0, length = 0;
QString id;
stream >> offset >> length >> id;
if (stream.status() != QDataStream::Ok) {
return result;
}
if (offset < 0 || length <= 0 || offset + length > textLength) {
return result;
}
result.push_back({ offset, length, id });
}
return result;
}
QString FlatTextarea::tagsMimeType() {
return qsl("application/x-td-field-tags");
}
FlatTextarea::FlatTextarea(QWidget *parent, const style::flatTextarea &st, const QString &pholder, const QString &v, const TagList &tags) : QTextEdit(parent)
, _phVisible(!v.length())
, a_phLeft(_phVisible ? 0 : st.phShift)
, a_phAlpha(_phVisible ? 1 : 0)
, a_phColor(st.phColor->c)
, _a_appearance(animation(this, &FlatTextarea::step_appearance))
, _lastTextWithTags { v, tags }
, _st(st) {
setAcceptRichText(false);
resize(_st.width, _st.font->height);
@ -62,28 +121,47 @@ FlatTextarea::FlatTextarea(QWidget *parent, const style::flatTextarea &st, const
_touchTimer.setSingleShot(true);
connect(&_touchTimer, SIGNAL(timeout()), this, SLOT(onTouchTimer()));
connect(document(), SIGNAL(contentsChange(int, int, int)), this, SLOT(onDocumentContentsChange(int, int, int)));
connect(document(), SIGNAL(contentsChange(int,int,int)), this, SLOT(onDocumentContentsChange(int,int,int)));
connect(document(), SIGNAL(contentsChanged()), this, SLOT(onDocumentContentsChanged()));
connect(this, SIGNAL(undoAvailable(bool)), this, SLOT(onUndoAvailable(bool)));
connect(this, SIGNAL(redoAvailable(bool)), this, SLOT(onRedoAvailable(bool)));
if (App::wnd()) connect(this, SIGNAL(selectionChanged()), App::wnd(), SLOT(updateGlobalMenu()));
if (!v.isEmpty()) {
setTextFast(v);
if (!_lastTextWithTags.text.isEmpty()) {
setTextWithTags(_lastTextWithTags, ClearUndoHistory);
}
}
void FlatTextarea::setTextFast(const QString &text, bool clearUndoHistory) {
if (clearUndoHistory) {
setPlainText(text);
FlatTextarea::TextWithTags FlatTextarea::getTextWithTagsPart(int start, int end) {
TextWithTags result;
result.text = getTextPart(start, end, &result.tags);
return result;
}
void FlatTextarea::setTextWithTags(const TextWithTags &textWithTags, UndoHistoryAction undoHistoryAction) {
_insertedTags = textWithTags.tags;
_insertedTagsAreFromMime = false;
_realInsertPosition = 0;
_realCharsAdded = textWithTags.text.size();
auto doc = document();
auto cursor = QTextCursor(doc->docHandle(), 0);
if (undoHistoryAction == ClearUndoHistory) {
doc->setUndoRedoEnabled(false);
cursor.beginEditBlock();
} else if (undoHistoryAction == MergeWithUndoHistory) {
cursor.joinPreviousEditBlock();
} else {
QTextCursor c(document()->docHandle(), 0);
c.joinPreviousEditBlock();
c.movePosition(QTextCursor::End, QTextCursor::KeepAnchor);
c.insertText(text);
c.movePosition(QTextCursor::End);
c.endEditBlock();
cursor.beginEditBlock();
}
cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor);
cursor.insertText(textWithTags.text);
cursor.movePosition(QTextCursor::End);
cursor.endEditBlock();
if (undoHistoryAction == ClearUndoHistory) {
doc->setUndoRedoEnabled(true);
}
_insertedTags.clear();
_realInsertPosition = -1;
finishPlaceholder();
}
@ -208,7 +286,8 @@ void FlatTextarea::paintEvent(QPaintEvent *e) {
p.setFont(_st.font);
p.setPen(a_phColor.current());
if (_st.phAlign == style::al_topleft && _phAfter > 0) {
p.drawText(_st.textMrg.left() - _fakeMargin + a_phLeft.current() + _st.font->width(getLastText().mid(0, _phAfter)), _st.textMrg.top() - _fakeMargin - st::lineWidth + _st.font->ascent, _ph);
int skipWidth = _st.font->width(getTextWithTags().text.mid(0, _phAfter));
p.drawText(_st.textMrg.left() - _fakeMargin + a_phLeft.current() + skipWidth, _st.textMrg.top() - _fakeMargin - st::lineWidth + _st.font->ascent, _ph);
} else {
QRect phRect(_st.textMrg.left() - _fakeMargin + _st.phPos.x() + a_phLeft.current(), _st.textMrg.top() - _fakeMargin + _st.phPos.y(), width() - _st.textMrg.left() - _st.textMrg.right(), height() - _st.textMrg.top() - _st.textMrg.bottom());
p.drawText(phRect, _ph, QTextOption(_st.phAlign));
@ -252,14 +331,14 @@ EmojiPtr FlatTextarea::getSingleEmoji() const {
return emojiFromUrl(imageName);
}
}
return 0;
return nullptr;
}
QString FlatTextarea::getInlineBotQuery(UserData **outInlineBot, QString *outInlineBotUsername) const {
t_assert(outInlineBot != nullptr);
t_assert(outInlineBotUsername != nullptr);
const QString &text(getLastText());
auto &text = getTextWithTags().text;
int32 inlineUsernameStart = 1, inlineUsernameLength = 0, size = text.size();
if (size > 2 && text.at(0) == '@' && text.at(1).isLetter()) {
@ -360,53 +439,72 @@ QString FlatTextarea::getMentionHashtagBotCommandPart(bool &start) const {
return QString();
}
void FlatTextarea::onMentionHashtagOrBotCommandInsert(QString str) {
QTextCursor c(textCursor());
int32 pos = c.position();
void FlatTextarea::insertTag(const QString &text, QString tagId) {
auto cursor = textCursor();
int32 pos = cursor.position();
QTextDocument *doc(document());
QTextBlock block = doc->findBlock(pos);
for (QTextBlock::Iterator iter = block.begin(); !iter.atEnd(); ++iter) {
QTextFragment fr(iter.fragment());
if (!fr.isValid()) continue;
auto doc = document();
auto block = doc->findBlock(pos);
for (auto iter = block.begin(); !iter.atEnd(); ++iter) {
auto fragment = iter.fragment();
t_assert(fragment.isValid());
int32 p = fr.position(), e = (p + fr.length());
if (p >= pos || e < pos) continue;
int fragmentPosition = fragment.position();
int fragmentEnd = (fragmentPosition + fragment.length());
if (fragmentPosition >= pos || fragmentEnd < pos) continue;
QTextCharFormat f = fr.charFormat();
if (f.isImageFormat()) continue;
auto format = fragment.charFormat();
if (format.isImageFormat()) continue;
bool mentionInCommand = false;
QString t(fr.text());
for (int i = pos - p; i > 0; --i) {
if (t.at(i - 1) == '@' || t.at(i - 1) == '#' || t.at(i - 1) == '/') {
if ((i == pos - p || (t.at(i - 1) == '/' ? t.at(i).isLetterOrNumber() : t.at(i).isLetter()) || t.at(i - 1) == '#') && (i < 2 || !(t.at(i - 2).isLetterOrNumber() || t.at(i - 2) == '_'))) {
c.setPosition(p + i - 1, QTextCursor::MoveAnchor);
int till = p + i;
for (; (till < e) && (till - p - i + 1 < str.size()); ++till) {
if (t.at(till - p).toLower() != str.at(till - p - i + 1).toLower()) {
auto fragmentText = fragment.text();
for (int i = pos - fragmentPosition; i > 0; --i) {
auto previousChar = fragmentText.at(i - 1);
if (previousChar == '@' || previousChar == '#' || previousChar == '/') {
if ((i == pos - fragmentPosition || (previousChar == '/' ? fragmentText.at(i).isLetterOrNumber() : fragmentText.at(i).isLetter()) || previousChar == '#') &&
(i < 2 || !(fragmentText.at(i - 2).isLetterOrNumber() || fragmentText.at(i - 2) == '_'))) {
cursor.setPosition(fragmentPosition + i - 1);
int till = fragmentPosition + i;
for (; (till < fragmentEnd); ++till) {
auto ch = fragmentText.at(till - fragmentPosition);
if (!ch.isLetterOrNumber() && ch != '_') {
break;
}
}
if (till - p - i + 1 == str.size() && till < e && t.at(till - p) == ' ') {
if (till < fragmentEnd && fragmentText.at(till - fragmentPosition) == ' ') {
++till;
}
c.setPosition(till, QTextCursor::KeepAnchor);
c.insertText(str + ' ');
return;
} else if ((i == pos - p || t.at(i).isLetter()) && t.at(i - 1) == '@' && i > 2 && (t.at(i - 2).isLetterOrNumber() || t.at(i - 2) == '_') && !mentionInCommand) {
cursor.setPosition(till, QTextCursor::KeepAnchor);
break;
} else if ((i == pos - fragmentPosition || fragmentText.at(i).isLetter()) && fragmentText.at(i - 1) == '@' && i > 2 && (fragmentText.at(i - 2).isLetterOrNumber() || fragmentText.at(i - 2) == '_') && !mentionInCommand) {
mentionInCommand = true;
--i;
continue;
}
break;
}
if (pos - p - i > 127 || (!mentionInCommand && (pos - p - i > 63))) break;
if (!t.at(i - 1).isLetterOrNumber() && t.at(i - 1) != '_') break;
if (pos - fragmentPosition - i > 127 || (!mentionInCommand && (pos - fragmentPosition - i > 63))) break;
if (!fragmentText.at(i - 1).isLetterOrNumber() && fragmentText.at(i - 1) != '_') break;
}
break;
}
c.insertText(str + ' ');
if (tagId.isEmpty()) {
QTextCharFormat format = cursor.charFormat();
format.setAnchor(false);
format.setAnchorName(QString());
format.clearForeground();
cursor.insertText(text + ' ', format);
} else {
_insertedTags.clear();
_insertedTags.push_back({ 0, text.size(), tagId });
_insertedTagsAreFromMime = false;
cursor.insertText(text + ' ');
_insertedTags.clear();
}
}
void FlatTextarea::setTagMimeProcessor(std_::unique_ptr<TagMimeProcessor> &&processor) {
_tagMimeProcessor = std_::move(processor);
}
void FlatTextarea::getSingleEmojiFragment(QString &text, QTextFragment &fragment) const {
@ -465,12 +563,79 @@ void FlatTextarea::removeSingleEmoji() {
}
}
QString FlatTextarea::getText(int32 start, int32 end) const {
namespace {
class TagAccumulator {
public:
TagAccumulator(FlatTextarea::TagList *tags) : _tags(tags) {
}
bool changed() const {
return _changed;
}
void feed(const QString &randomTagId, int currentPosition) {
if (randomTagId == _currentTagId) return;
if (!_currentTagId.isEmpty()) {
int randomPartPosition = _currentTagId.lastIndexOf('/');
t_assert(randomPartPosition > 0);
bool tagChanged = true;
if (_currentTag < _tags->size()) {
auto &alreadyTag = _tags->at(_currentTag);
if (alreadyTag.offset == _currentStart &&
alreadyTag.length == currentPosition - _currentStart &&
alreadyTag.id == _currentTagId.midRef(0, randomPartPosition)) {
tagChanged = false;
}
}
if (tagChanged) {
_changed = true;
FlatTextarea::Tag tag = {
_currentStart,
currentPosition - _currentStart,
_currentTagId.mid(0, randomPartPosition),
};
if (_currentTag < _tags->size()) {
(*_tags)[_currentTag] = tag;
} else {
_tags->push_back(tag);
}
}
++_currentTag;
}
_currentTagId = randomTagId;
_currentStart = currentPosition;
};
void finish() {
if (_currentTag < _tags->size()) {
_tags->resize(_currentTag);
_changed = true;
}
}
private:
FlatTextarea::TagList *_tags;
bool _changed = false;
int _currentTag = 0;
int _currentStart = 0;
QString _currentTagId;
};
} // namespace
QString FlatTextarea::getTextPart(int start, int end, TagList *outTagsList, bool *outTagsChanged) const {
if (end >= 0 && end <= start) return QString();
if (start < 0) start = 0;
bool full = (start == 0) && (end < 0);
TagAccumulator tagAccumulator(outTagsList);
QTextDocument *doc(document());
QTextBlock from = full ? doc->begin() : doc->findBlock(start), till = (end < 0) ? doc->end() : doc->findBlock(end);
if (till.isValid()) till = till.next();
@ -485,17 +650,28 @@ QString FlatTextarea::getText(int32 start, int32 end) const {
end = possibleLen;
}
for (QTextBlock b = from; b != till; b = b.next()) {
for (QTextBlock::Iterator iter = b.begin(); !iter.atEnd(); ++iter) {
bool tillFragmentEnd = full;
for (auto b = from; b != till; b = b.next()) {
for (auto iter = b.begin(); !iter.atEnd(); ++iter) {
QTextFragment fragment(iter.fragment());
if (!fragment.isValid()) continue;
int32 p = full ? 0 : fragment.position(), e = full ? 0 : (p + fragment.length());
if (!full) {
if (p >= end || e <= start) {
tillFragmentEnd = (e <= end);
if (p == end) {
tagAccumulator.feed(fragment.charFormat().anchorName(), result.size());
}
if (p >= end) {
break;
}
if (e <= start) {
continue;
}
}
if (full || p >= start) {
tagAccumulator.feed(fragment.charFormat().anchorName(), result.size());
}
QTextCharFormat f = fragment.charFormat();
QString emojiText;
@ -539,6 +715,13 @@ QString FlatTextarea::getText(int32 start, int32 end) const {
result.append('\n');
}
result.chop(1);
if (tillFragmentEnd) tagAccumulator.feed(QString(), result.size());
tagAccumulator.finish();
if (outTagsChanged) {
*outTagsChanged = tagAccumulator.changed();
}
return result;
}
@ -638,7 +821,7 @@ void FlatTextarea::parseLinks() { // some code is duplicated in text.cpp!
continue;
}
}
newLinks.push_back(qMakePair(domainOffset - 1, p - start - domainOffset + 2));
newLinks.push_back({ domainOffset - 1, static_cast<int>(p - start - domainOffset + 2) });
offset = matchOffset = p - start;
}
@ -652,19 +835,32 @@ 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));
for_const (auto &link, _links) {
result.push_back(text.mid(link.start + 1, link.length - 2));
}
}
return result;
}
void FlatTextarea::insertFromMimeData(const QMimeData *source) {
auto mime = tagsMimeType();
auto text = source->text();
if (source->hasFormat(mime)) {
auto tagsData = source->data(mime);
_insertedTags = deserializeTagsList(tagsData, text.size());
_insertedTagsAreFromMime = true;
} else {
_insertedTags.clear();
}
auto cursor = textCursor();
_realInsertPosition = qMin(cursor.position(), cursor.anchor());
_realCharsAdded = text.size();
QTextEdit::insertFromMimeData(source);
if (!_inDrop) emit spacedReturnedPasted();
}
void FlatTextarea::correctValue(const QString &was, QString &now) {
if (!_inDrop) {
emit spacedReturnedPasted();
_insertedTags.clear();
_realInsertPosition = -1;
}
}
void FlatTextarea::insertEmoji(EmojiPtr emoji, QTextCursor c) {
@ -674,7 +870,11 @@ void FlatTextarea::insertEmoji(EmojiPtr emoji, QTextCursor c) {
imageFormat.setHeight(eh / cIntRetinaFactor());
imageFormat.setName(qsl("emoji://e.") + QString::number(emojiKey(emoji), 16));
imageFormat.setVerticalAlignment(QTextCharFormat::AlignBaseline);
if (c.charFormat().isAnchor()) {
imageFormat.setAnchor(true);
imageFormat.setAnchorName(c.charFormat().anchorName());
imageFormat.setForeground(st::defaultTextStyle.linkFg);
}
static QString objectReplacement(QChar::ObjectReplacementCharacter);
c.insertText(objectReplacement, imageFormat);
}
@ -695,89 +895,240 @@ void FlatTextarea::checkContentHeight() {
}
}
void FlatTextarea::processDocumentContentsChange(int position, int charsAdded) {
int32 replacePosition = -1, replaceLen = 0;
const EmojiData *emoji = 0;
namespace {
static QString regular = qsl("Open Sans"), semibold = qsl("Open Sans Semibold");
bool checkTilde = !cRetina() && (font().pixelSize() == 13) && (font().family() == regular), wasTildeFragment = false;
// Optimization: with null page size document does not re-layout
// on each insertText / mergeCharFormat.
void prepareFormattingOptimization(QTextDocument *document) {
if (!document->pageSize().isNull()) {
document->setPageSize(QSizeF(0, 0));
}
}
QTextDocument *doc(document());
void removeTags(QTextDocument *document, int from, int end) {
QTextCursor c(document->docHandle(), 0);
c.setPosition(from);
c.setPosition(end, QTextCursor::KeepAnchor);
QTextCharFormat format;
format.setAnchor(false);
format.setAnchorName(QString());
format.setForeground(st::black);
c.mergeCharFormat(format);
}
// Returns the position of the first inserted tag or "changedEnd" value if none found.
int processInsertedTags(QTextDocument *document, int changedPosition, int changedEnd, const FlatTextarea::TagList &tags, FlatTextarea::TagMimeProcessor *processor) {
int firstTagStart = changedEnd;
int applyNoTagFrom = changedEnd;
for_const (auto &tag, tags) {
int tagFrom = changedPosition + tag.offset;
int tagTo = tagFrom + tag.length;
accumulate_max(tagFrom, changedPosition);
accumulate_min(tagTo, changedEnd);
auto tagId = processor ? processor->tagFromMimeTag(tag.id) : tag.id;
if (tagTo > tagFrom && !tagId.isEmpty()) {
accumulate_min(firstTagStart, tagFrom);
prepareFormattingOptimization(document);
if (applyNoTagFrom < tagFrom) {
removeTags(document, applyNoTagFrom, tagFrom);
}
QTextCursor c(document->docHandle(), 0);
c.setPosition(tagFrom);
c.setPosition(tagTo, QTextCursor::KeepAnchor);
QTextCharFormat format;
format.setAnchor(true);
format.setAnchorName(tagId + '/' + QString::number(rand_value<uint32>()));
format.setForeground(st::defaultTextStyle.linkFg);
c.mergeCharFormat(format);
applyNoTagFrom = tagTo;
}
}
if (applyNoTagFrom < changedEnd) {
removeTags(document, applyNoTagFrom, changedEnd);
}
return firstTagStart;
}
// When inserting a part of text inside a tag we need to have
// a way to know if the insertion replaced the end of the tag
// or it was strictly inside (in the middle) of the tag.
bool wasInsertTillTheEndOfTag(QTextBlock block, QTextBlock::iterator fragmentIt, int insertionEnd) {
auto insertTagName = fragmentIt.fragment().charFormat().anchorName();
while (true) {
int32 start = position, end = position + charsAdded;
QTextBlock from = doc->findBlock(start), till = doc->findBlock(end);
if (till.isValid()) till = till.next();
for (; !fragmentIt.atEnd(); ++fragmentIt) {
auto fragment = fragmentIt.fragment();
bool fragmentOutsideInsertion = (fragment.position() >= insertionEnd);
if (fragmentOutsideInsertion) {
return (fragment.charFormat().anchorName() != insertTagName);
}
int fragmentEnd = fragment.position() + fragment.length();
bool notFullFragmentInserted = (fragmentEnd > insertionEnd);
if (notFullFragmentInserted) {
return false;
}
}
if (block.isValid()) {
fragmentIt = block.begin();
block = block.next();
} else {
break;
}
}
// Insertion goes till the end of the text => not strictly inside a tag.
return true;
}
for (QTextBlock b = from; b != till; b = b.next()) {
for (QTextBlock::Iterator iter = b.begin(); !iter.atEnd(); ++iter) {
QTextFragment fragment(iter.fragment());
if (!fragment.isValid()) continue;
struct FormattingAction {
enum class Type {
Invalid,
InsertEmoji,
TildeFont,
RemoveTag,
};
Type type = Type::Invalid;
EmojiPtr emoji = nullptr;
bool isTilde = false;
int intervalStart = 0;
int intervalEnd = 0;
};
int32 fp = fragment.position(), fe = fp + fragment.length();
if (fp >= end || fe <= start) {
} // namespace
void FlatTextarea::processFormatting(int insertPosition, int insertEnd) {
// Tilde formatting.
auto regularFont = qsl("Open Sans"), semiboldFont = qsl("Open Sans Semibold");
bool tildeFormatting = !cRetina() && (font().pixelSize() == 13) && (font().family() == regularFont);
bool isTildeFragment = false;
// First tag handling (the one we inserted text to).
bool startTagFound = false;
bool breakTagOnNotLetter = false;
auto doc = document();
// Apply inserted tags.
auto insertedTagsProcessor = _insertedTagsAreFromMime ? _tagMimeProcessor.get() : nullptr;
int breakTagOnNotLetterTill = processInsertedTags(doc, insertPosition, insertEnd,
_insertedTags, insertedTagsProcessor);
using ActionType = FormattingAction::Type;
while (true) {
FormattingAction action;
auto fromBlock = doc->findBlock(insertPosition);
auto tillBlock = doc->findBlock(insertEnd);
if (tillBlock.isValid()) tillBlock = tillBlock.next();
for (auto block = fromBlock; block != tillBlock; block = block.next()) {
for (auto fragmentIt = block.begin(); !fragmentIt.atEnd(); ++fragmentIt) {
auto fragment = fragmentIt.fragment();
t_assert(fragment.isValid());
int fragmentPosition = fragment.position();
if (insertPosition >= fragmentPosition + fragment.length()) {
continue;
}
if (checkTilde) {
wasTildeFragment = (fragment.charFormat().fontFamily() == semibold);
int changedPositionInFragment = insertPosition - fragmentPosition; // Can be negative.
int changedEndInFragment = insertEnd - fragmentPosition;
if (changedEndInFragment <= 0) {
break;
}
QString t(fragment.text());
const QChar *ch = t.constData(), *e = ch + t.size();
for (; ch != e; ++ch, ++fp) {
int32 emojiLen = 0;
emoji = emojiFromText(ch, e, &emojiLen);
if (emoji) {
if (replacePosition >= 0) {
emoji = 0; // replace tilde char format first
} else {
replacePosition = fp;
replaceLen = emojiLen;
auto charFormat = fragment.charFormat();
if (tildeFormatting) {
isTildeFragment = (charFormat.fontFamily() == semiboldFont);
}
auto fragmentText = fragment.text();
auto *textStart = fragmentText.constData();
auto *textEnd = textStart + fragmentText.size();
if (!startTagFound) {
startTagFound = true;
auto tagName = charFormat.anchorName();
if (!tagName.isEmpty()) {
breakTagOnNotLetter = wasInsertTillTheEndOfTag(block, fragmentIt, insertEnd);
}
}
auto *ch = textStart + qMax(changedPositionInFragment, 0);
for (; ch < textEnd; ++ch) {
int emojiLength = 0;
if (auto emoji = emojiFromText(ch, textEnd, &emojiLength)) {
// Replace emoji if no current action is prepared.
if (action.type == ActionType::Invalid) {
action.type = ActionType::InsertEmoji;
action.emoji = emoji;
action.intervalStart = fragmentPosition + (ch - textStart);
action.intervalEnd = action.intervalStart + emojiLength;
}
break;
}
if (checkTilde && fp >= position) { // tilde fix in OpenSans
if (breakTagOnNotLetter && !ch->isLetter()) {
// Remove tag name till the end if no current action is prepared.
if (action.type != ActionType::Invalid) {
break;
}
breakTagOnNotLetter = false;
if (fragmentPosition + (ch - textStart) < breakTagOnNotLetterTill) {
action.type = ActionType::RemoveTag;
action.intervalStart = fragmentPosition + (ch - textStart);
action.intervalEnd = breakTagOnNotLetterTill;
break;
}
}
if (tildeFormatting) { // Tilde symbol fix in OpenSans.
bool tilde = (ch->unicode() == '~');
if ((tilde && !wasTildeFragment) || (!tilde && wasTildeFragment)) {
if (replacePosition < 0) {
replacePosition = fp;
replaceLen = 1;
if ((tilde && !isTildeFragment) || (!tilde && isTildeFragment)) {
if (action.type == ActionType::Invalid) {
action.type = ActionType::TildeFont;
action.intervalStart = fragmentPosition + (ch - textStart);
action.intervalEnd = action.intervalStart + 1;
action.isTilde = tilde;
} else {
++replaceLen;
++action.intervalEnd;
}
} else if (replacePosition >= 0) {
} else if (action.type == ActionType::TildeFont) {
break;
}
}
if (ch + 1 < e && ch->isHighSurrogate() && (ch + 1)->isLowSurrogate()) {
if (ch + 1 < textEnd && ch->isHighSurrogate() && (ch + 1)->isLowSurrogate()) {
++ch;
++fp;
++fragmentPosition;
}
}
if (replacePosition >= 0) break;
if (action.type != ActionType::Invalid) break;
}
if (replacePosition >= 0) break;
if (action.type != ActionType::Invalid) break;
}
if (replacePosition >= 0) {
if (!document()->pageSize().isNull()) {
document()->setPageSize(QSizeF(0, 0));
}
QTextCursor c(doc->docHandle(), replacePosition);
c.setPosition(replacePosition + replaceLen, QTextCursor::KeepAnchor);
if (emoji) {
insertEmoji(emoji, c);
} else {
QTextCharFormat format;
format.setFontFamily(wasTildeFragment ? regular : semibold);
c.mergeCharFormat(format);
}
charsAdded -= replacePosition + replaceLen - position;
position = replacePosition + (emoji ? 1 : replaceLen);
if (action.type != ActionType::Invalid) {
prepareFormattingOptimization(doc);
emoji = 0;
replacePosition = -1;
QTextCursor c(doc->docHandle(), 0);
c.setPosition(action.intervalStart);
c.setPosition(action.intervalEnd, QTextCursor::KeepAnchor);
if (action.type == ActionType::InsertEmoji) {
insertEmoji(action.emoji, c);
insertPosition = action.intervalStart + 1;
} else if (action.type == ActionType::RemoveTag) {
QTextCharFormat format;
format.setAnchor(false);
format.setAnchorName(QString());
format.setForeground(st::black);
c.mergeCharFormat(format);
} else if (action.type == ActionType::TildeFont) {
QTextCharFormat format;
format.setFontFamily(action.isTilde ? semiboldFont : regularFont);
c.mergeCharFormat(format);
insertPosition = action.intervalEnd;
}
} else {
break;
}
@ -787,6 +1138,12 @@ void FlatTextarea::processDocumentContentsChange(int position, int charsAdded) {
void FlatTextarea::onDocumentContentsChange(int position, int charsRemoved, int charsAdded) {
if (_correcting) return;
int insertPosition = (_realInsertPosition >= 0) ? _realInsertPosition : position;
int insertLength = (_realInsertPosition >= 0) ? _realCharsAdded : charsAdded;
int removePosition = position;
int removeLength = charsRemoved;
QTextCursor(document()->docHandle(), 0).joinPreviousEditBlock();
_correcting = true;
@ -795,31 +1152,32 @@ void FlatTextarea::onDocumentContentsChange(int position, int charsRemoved, int
c.movePosition(QTextCursor::End);
int32 fullSize = c.position(), toRemove = fullSize - _maxLength;
if (toRemove > 0) {
if (toRemove > charsAdded) {
if (charsAdded) {
c.setPosition(position);
c.setPosition((position + charsAdded), QTextCursor::KeepAnchor);
if (toRemove > insertLength) {
if (insertLength) {
c.setPosition(insertPosition);
c.setPosition((insertPosition + insertLength), QTextCursor::KeepAnchor);
c.removeSelectedText();
}
c.setPosition(fullSize - (toRemove - charsAdded));
c.setPosition(fullSize - (toRemove - insertLength));
c.setPosition(fullSize, QTextCursor::KeepAnchor);
c.removeSelectedText();
} else {
c.setPosition(position + (charsAdded - toRemove));
c.setPosition(position + charsAdded, QTextCursor::KeepAnchor);
c.setPosition(insertPosition + (insertLength - toRemove));
c.setPosition(insertPosition + insertLength, QTextCursor::KeepAnchor);
c.removeSelectedText();
}
}
}
_correcting = false;
if (insertPosition == removePosition) {
if (!_links.isEmpty()) {
bool changed = false;
for (LinkRanges::iterator i = _links.begin(); i != _links.end();) {
if (i->first + i->second <= position) {
for (auto i = _links.begin(); i != _links.end();) {
if (i->start + i->length <= insertPosition) {
++i;
} else if (i->first >= position + charsRemoved) {
i->first += charsAdded - charsRemoved;
} else if (i->start >= removePosition + removeLength) {
i->start += insertLength - removeLength;
++i;
} else {
i = _links.erase(i);
@ -828,30 +1186,25 @@ void FlatTextarea::onDocumentContentsChange(int position, int charsRemoved, int
}
if (changed) emit linksChanged();
}
} else {
parseLinks();
}
if (document()->availableRedoSteps() > 0) {
QTextCursor(document()->docHandle(), 0).endEditBlock();
return;
}
const int takeBack = 3;
position -= takeBack;
charsAdded += takeBack;
if (position < 0) {
charsAdded += position;
position = 0;
}
if (charsAdded <= 0) {
if (insertLength <= 0) {
QTextCursor(document()->docHandle(), 0).endEditBlock();
return;
}
_correcting = true;
QSizeF s = document()->pageSize();
processDocumentContentsChange(position, charsAdded);
if (document()->pageSize() != s) {
document()->setPageSize(s);
auto pageSize = document()->pageSize();
processFormatting(insertPosition, insertPosition + insertLength);
if (document()->pageSize() != pageSize) {
document()->setPageSize(pageSize);
}
_correcting = false;
@ -861,12 +1214,16 @@ void FlatTextarea::onDocumentContentsChange(int position, int charsRemoved, int
void FlatTextarea::onDocumentContentsChanged() {
if (_correcting) return;
QString curText(getText());
auto tagsChanged = false;
auto curText = getTextPart(0, -1, &_lastTextWithTags.tags, &tagsChanged);
_correcting = true;
correctValue(_oldtext, curText);
correctValue(_lastTextWithTags.text, curText, _lastTextWithTags.tags);
_correcting = false;
if (_oldtext != curText) {
_oldtext = curText;
bool textOrTagsChanged = tagsChanged || (_lastTextWithTags.text != curText);
if (textOrTagsChanged) {
_lastTextWithTags.text = curText;
emit changed();
checkContentHeight();
}
@ -908,12 +1265,16 @@ void FlatTextarea::setPlaceholder(const QString &ph, int32 afterSymbols) {
_phAfter = afterSymbols;
updatePlaceholder();
}
_phelided = _st.font->elided(_ph, width() - _st.textMrg.left() - _st.textMrg.right() - _st.phPos.x() - 1 - (_phAfter ? _st.font->width(getLastText().mid(0, _phAfter)) : 0));
int skipWidth = 0;
if (_phAfter) {
skipWidth = _st.font->width(getTextWithTags().text.mid(0, _phAfter));
}
_phelided = _st.font->elided(_ph, width() - _st.textMrg.left() - _st.textMrg.right() - _st.phPos.x() - 1 - skipWidth);
if (_phVisible) update();
}
void FlatTextarea::updatePlaceholder() {
bool vis = (getLastText().size() <= _phAfter);
bool vis = (getTextWithTags().text.size() <= _phAfter);
if (vis == _phVisible) return;
a_phLeft.start(vis ? 0 : _st.phShift);
@ -928,7 +1289,16 @@ QMimeData *FlatTextarea::createMimeDataFromSelection() const {
QTextCursor c(textCursor());
int32 start = c.selectionStart(), end = c.selectionEnd();
if (end > start) {
result->setText(getText(start, end));
TagList tags;
result->setText(getTextPart(start, end, &tags));
if (!tags.isEmpty()) {
if (_tagMimeProcessor) {
for (auto &tag : tags) {
tag.id = _tagMimeProcessor->mimeTagFromTag(tag.id);
}
}
result->setData(tagsMimeType(), serializeTagsList(tags));
}
}
return result;
}
@ -1015,6 +1385,9 @@ void FlatTextarea::dropEvent(QDropEvent *e) {
_inDrop = true;
QTextEdit::dropEvent(e);
_inDrop = false;
_insertedTags.clear();
_realInsertPosition = -1;
emit spacedReturnedPasted();
}

View file

@ -31,26 +31,27 @@ class FlatTextarea : public QTextEdit {
public:
FlatTextarea(QWidget *parent, const style::flatTextarea &st, const QString &ph = QString(), const QString &val = QString());
struct Tag {
int offset, length;
QString id;
};
using TagList = QVector<Tag>;
struct TextWithTags {
using Tags = FlatTextarea::TagList;
QString text;
Tags tags;
};
bool viewportEvent(QEvent *e) override;
void touchEvent(QTouchEvent *e);
void paintEvent(QPaintEvent *e) override;
void focusInEvent(QFocusEvent *e) override;
void focusOutEvent(QFocusEvent *e) override;
void keyPressEvent(QKeyEvent *e) override;
void resizeEvent(QResizeEvent *e) override;
void mousePressEvent(QMouseEvent *e) override;
void dropEvent(QDropEvent *e) override;
void contextMenuEvent(QContextMenuEvent *e) override;
static QByteArray serializeTagsList(const TagList &tags);
static TagList deserializeTagsList(QByteArray data, int textLength);
static QString tagsMimeType();
FlatTextarea(QWidget *parent, const style::flatTextarea &st, const QString &ph = QString(), const QString &val = QString(), const TagList &tags = TagList());
void setMaxLength(int32 maxLength);
void setMinHeight(int32 minHeight);
void setMaxHeight(int32 maxHeight);
const QString &getLastText() const {
return _oldtext;
}
void setPlaceholder(const QString &ph, int32 afterSymbols = 0);
void updatePlaceholder();
void finishPlaceholder();
@ -92,7 +93,33 @@ public:
};
void setSubmitSettings(SubmitSettings settings);
void setTextFast(const QString &text, bool clearUndoHistory = true);
const TextWithTags &getTextWithTags() const {
return _lastTextWithTags;
}
TextWithTags getTextWithTagsPart(int start, int end = -1);
void insertTag(const QString &text, QString tagId = QString());
bool isEmpty() const {
return _lastTextWithTags.text.isEmpty();
}
enum UndoHistoryAction {
AddToUndoHistory,
MergeWithUndoHistory,
ClearUndoHistory
};
void setTextWithTags(const TextWithTags &textWithTags, UndoHistoryAction undoHistoryAction = AddToUndoHistory);
// If you need to make some preparations of tags before putting them to QMimeData
// (and then to clipboard or to drag-n-drop object), here is a strategy for that.
class TagMimeProcessor {
public:
virtual QString mimeTagFromTag(const QString &tagId) = 0;
virtual QString tagFromMimeTag(const QString &mimeTag) = 0;
virtual ~TagMimeProcessor() {
}
};
void setTagMimeProcessor(std_::unique_ptr<TagMimeProcessor> &&processor);
public slots:
@ -104,8 +131,6 @@ public slots:
void onUndoAvailable(bool avail);
void onRedoAvailable(bool avail);
void onMentionHashtagOrBotCommandInsert(QString str);
signals:
void resized();
@ -118,8 +143,19 @@ signals:
protected:
QString getText(int32 start = 0, int32 end = -1) const;
virtual void correctValue(const QString &was, QString &now);
bool viewportEvent(QEvent *e) override;
void touchEvent(QTouchEvent *e);
void paintEvent(QPaintEvent *e) override;
void focusInEvent(QFocusEvent *e) override;
void focusOutEvent(QFocusEvent *e) override;
void keyPressEvent(QKeyEvent *e) override;
void resizeEvent(QResizeEvent *e) override;
void mousePressEvent(QMouseEvent *e) override;
void dropEvent(QDropEvent *e) override;
void contextMenuEvent(QContextMenuEvent *e) override;
virtual void correctValue(const QString &was, QString &now, TagList &nowTags) {
}
void insertEmoji(EmojiPtr emoji, QTextCursor c);
@ -129,8 +165,21 @@ protected:
private:
// "start" and "end" are in coordinates of text where emoji are replaced
// by ObjectReplacementCharacter. If "end" = -1 means get text till the end.
QString getTextPart(int start, int end, TagList *outTagsList, bool *outTagsChanged = nullptr) const;
void getSingleEmojiFragment(QString &text, QTextFragment &fragment) const;
void processDocumentContentsChange(int position, int charsAdded);
// After any characters added we must postprocess them. This includes:
// 1. Replacing font family to semibold for ~ characters, if we used Open Sans 13px.
// 2. Replacing font family from semibold for all non-~ characters, if we used ...
// 3. Replacing emoji code sequences by ObjectReplacementCharacters with emoji pics.
// 4. Interrupting tags in which the text was inserted by any char except a letter.
// 5. Applying tags from "_insertedTags" in case we pasted text with tags, not just text.
// Rule 4 applies only if we inserted chars not in the middle of a tag (but at the end).
void processFormatting(int changedPosition, int changedEnd);
bool heightAutoupdated();
int _minHeight = -1; // < 0 - no autosize
@ -138,7 +187,7 @@ private:
int _maxLength = -1;
SubmitSettings _submitSettings = SubmitSettings::Enter;
QString _ph, _phelided, _oldtext;
QString _ph, _phelided;
int _phAfter = 0;
bool _phVisible;
anim::ivalue a_phLeft;
@ -146,6 +195,19 @@ private:
anim::cvalue a_phColor;
Animation _a_appearance;
TextWithTags _lastTextWithTags;
// Tags list which we should apply while setText() call or insert from mime data.
TagList _insertedTags;
bool _insertedTagsAreFromMime;
// Override insert position and charsAdded from complex text editing
// (like drag-n-drop in the same text edit field).
int _realInsertPosition = -1;
int _realCharsAdded = 0;
std_::unique_ptr<TagMimeProcessor> _tagMimeProcessor;
style::flatTextarea _st;
bool _undoAvailable = false;
@ -163,7 +225,33 @@ private:
bool _correcting = false;
typedef QPair<int, int> LinkRange;
typedef QList<LinkRange> LinkRanges;
struct LinkRange {
int start;
int length;
};
friend bool operator==(const LinkRange &a, const LinkRange &b);
friend bool operator!=(const LinkRange &a, const LinkRange &b);
using LinkRanges = QVector<LinkRange>;
LinkRanges _links;
};
inline bool operator==(const FlatTextarea::Tag &a, const FlatTextarea::Tag &b) {
return (a.offset == b.offset) && (a.length == b.length) && (a.id == b.id);
}
inline bool operator!=(const FlatTextarea::Tag &a, const FlatTextarea::Tag &b) {
return !(a == b);
}
inline bool operator==(const FlatTextarea::TextWithTags &a, const FlatTextarea::TextWithTags &b) {
return (a.text == b.text) && (a.tags == b.tags);
}
inline bool operator!=(const FlatTextarea::TextWithTags &a, const FlatTextarea::TextWithTags &b) {
return !(a == b);
}
inline bool operator==(const FlatTextarea::LinkRange &a, const FlatTextarea::LinkRange &b) {
return (a.start == b.start) && (a.length == b.length);
}
inline bool operator!=(const FlatTextarea::LinkRange &a, const FlatTextarea::LinkRange &b) {
return !(a == b);
}

View file

@ -242,27 +242,6 @@ public:
createBlock();
}
void getLinkData(const QString &original, QString &result, int32 &fullDisplayed) {
if (!original.isEmpty() && original.at(0) == '/') {
result = original;
fullDisplayed = -4; // bot command
} else if (!original.isEmpty() && original.at(0) == '@') {
result = original;
fullDisplayed = -3; // mention
} else if (!original.isEmpty() && original.at(0) == '#') {
result = original;
fullDisplayed = -2; // hashtag
} else if (reMailStart().match(original).hasMatch()) {
result = original;
fullDisplayed = -1; // email
} else {
QUrl url(original), good(url.isValid() ? url.toEncoded() : "");
QString readable = good.isValid() ? good.toDisplayString() : original;
result = _t->_font->elided(readable, st::linkCropLimit);
fullDisplayed = (result == readable) ? 1 : 0;
}
}
bool checkCommand() {
bool result = false;
for (QChar c = ((ptr < end) ? *ptr : 0); c == TextCommand; c = ((ptr < end) ? *ptr : 0)) {
@ -274,7 +253,8 @@ public:
return result;
}
void checkEntities() {
// Returns true if at least one entity was parsed in the current position.
bool checkEntities() {
while (!removeFlags.isEmpty() && (ptr >= removeFlags.firstKey() || ptr >= end)) {
const QList<int32> &removing(removeFlags.first());
for (int32 i = removing.size(); i > 0;) {
@ -289,49 +269,59 @@ public:
}
removeFlags.erase(removeFlags.begin());
}
while (waitingEntity != entitiesEnd && start + waitingEntity->offset + waitingEntity->length <= ptr) {
while (waitingEntity != entitiesEnd && start + waitingEntity->offset() + waitingEntity->length() <= ptr) {
++waitingEntity;
}
if (waitingEntity == entitiesEnd || ptr < start + waitingEntity->offset) {
return;
if (waitingEntity == entitiesEnd || ptr < start + waitingEntity->offset()) {
return false;
}
bool lnk = false;
int32 startFlags = 0;
int32 fullDisplayed;
QString lnkUrl, lnkText;
if (waitingEntity->type == EntityInTextCustomUrl) {
lnk = true;
lnkUrl = waitingEntity->text;
lnkText = QString(start + waitingEntity->offset, waitingEntity->length);
fullDisplayed = -5;
} else if (waitingEntity->type == EntityInTextBold) {
QString linkData, linkText;
auto type = waitingEntity->type(), linkType = EntityInTextInvalid;
LinkDisplayStatus linkDisplayStatus = LinkDisplayedFull;
if (type == EntityInTextBold) {
startFlags = TextBlockFSemibold;
} else if (waitingEntity->type == EntityInTextItalic) {
} else if (type == EntityInTextItalic) {
startFlags = TextBlockFItalic;
} else if (waitingEntity->type == EntityInTextCode) {
} else if (type == EntityInTextCode) {
startFlags = TextBlockFCode;
} else if (waitingEntity->type == EntityInTextPre) {
} else if (type == EntityInTextPre) {
startFlags = TextBlockFPre;
createBlock();
if (!_t->_blocks.isEmpty() && _t->_blocks.back()->type() != TextBlockTNewline) {
createNewlineBlock();
}
} else if (type == EntityInTextUrl
|| type == EntityInTextEmail
|| type == EntityInTextMention
|| type == EntityInTextHashtag
|| type == EntityInTextBotCommand) {
linkType = type;
linkData = QString(start + waitingEntity->offset(), waitingEntity->length());
if (linkType == EntityInTextUrl) {
computeLinkText(linkData, &linkText, &linkDisplayStatus);
} else {
lnk = true;
lnkUrl = QString(start + waitingEntity->offset, waitingEntity->length);
getLinkData(lnkUrl, lnkText, fullDisplayed);
linkText = linkData;
}
} else if (type == EntityInTextCustomUrl || type == EntityInTextMentionName) {
linkType = type;
linkData = waitingEntity->data();
linkText = QString(start + waitingEntity->offset(), waitingEntity->length());
}
if (lnk) {
if (linkType != EntityInTextInvalid) {
createBlock();
links.push_back(TextLinkData(lnkUrl, fullDisplayed));
links.push_back(TextLinkData(linkType, linkText, linkData, linkDisplayStatus));
lnkIndex = 0x8000 + links.size();
_t->_text += lnkText;
ptr = start + waitingEntity->offset + waitingEntity->length;
for (auto entityEnd = start + waitingEntity->offset() + waitingEntity->length(); ptr < entityEnd; ++ptr) {
parseCurrentChar();
parseEmojiFromCurrent();
if (sumFinished || _t->_text.size() >= 0x8000) break; // 32k max
}
createBlock();
lnkIndex = 0;
@ -339,25 +329,27 @@ public:
if (!(flags & startFlags)) {
createBlock();
flags |= startFlags;
removeFlags[start + waitingEntity->offset + waitingEntity->length].push_front(startFlags);
removeFlags[start + waitingEntity->offset() + waitingEntity->length()].push_front(startFlags);
}
}
++waitingEntity;
if (links.size() >= 0x7FFF) {
while (waitingEntity != entitiesEnd && (
waitingEntity->type == EntityInTextUrl ||
waitingEntity->type == EntityInTextCustomUrl ||
waitingEntity->type == EntityInTextEmail ||
waitingEntity->type == EntityInTextHashtag ||
waitingEntity->type == EntityInTextMention ||
waitingEntity->type == EntityInTextBotCommand ||
waitingEntity->length <= 0)) {
waitingEntity->type() == EntityInTextUrl ||
waitingEntity->type() == EntityInTextCustomUrl ||
waitingEntity->type() == EntityInTextEmail ||
waitingEntity->type() == EntityInTextHashtag ||
waitingEntity->type() == EntityInTextMention ||
waitingEntity->type() == EntityInTextMentionName ||
waitingEntity->type() == EntityInTextBotCommand ||
waitingEntity->length() <= 0)) {
++waitingEntity;
}
} else {
while (waitingEntity != entitiesEnd && waitingEntity->length <= 0) ++waitingEntity;
while (waitingEntity != entitiesEnd && waitingEntity->length() <= 0) ++waitingEntity;
}
return true;
}
bool readSkipBlockCommand() {
@ -455,7 +447,7 @@ public:
case TextCommandLinkText: {
createBlock();
int32 len = ptr->unicode();
links.push_back(TextLinkData(QString(++ptr, len), false));
links.push_back(TextLinkData(EntityInTextCustomUrl, QString(), QString(++ptr, len), LinkDisplayedFull));
lnkIndex = 0x8000 + links.size();
} break;
@ -559,18 +551,19 @@ public:
lnkIndex(0),
stopAfterWidth(QFIXED_MAX) {
if (options.flags & TextParseLinks) {
entities = textParseEntities(src, options.flags, rich);
textParseEntities(src, options.flags, &entities, rich);
}
parse(options);
}
TextParser(Text *t, const QString &text, const EntitiesInText &preparsed, const TextParseOptions &options) : _t(t),
src(text),
TextParser(Text *t, const TextWithEntities &textWithEntities, const TextParseOptions &options) : _t(t),
src(textWithEntities.text),
rich(options.flags & TextParseRichText),
multiline(options.flags & TextParseMultiline),
maxLnkIndex(0),
flags(0),
lnkIndex(0),
stopAfterWidth(QFIXED_MAX) {
auto preparsed = textWithEntities.entities;
if ((options.flags & TextParseLinks) && !preparsed.isEmpty()) {
bool parseMentions = (options.flags & TextParseMentions);
bool parseHashtags = (options.flags & TextParseHashtags);
@ -581,13 +574,13 @@ public:
} else {
int32 i = 0, l = preparsed.size();
entities.reserve(l);
const QChar s = text.size();
const QChar s = src.size();
for (; i < l; ++i) {
EntityInTextType t = preparsed.at(i).type;
if ((t == EntityInTextMention && !parseMentions) ||
(t == EntityInTextHashtag && !parseHashtags) ||
(t == EntityInTextBotCommand && !parseBotCommands) ||
((t == EntityInTextBold || t == EntityInTextItalic || t == EntityInTextCode || t == EntityInTextPre) && !parseMono)) {
auto type = preparsed.at(i).type();
if (((type == EntityInTextMention || type == EntityInTextMentionName) && !parseMentions) ||
(type == EntityInTextHashtag && !parseHashtags) ||
(type == EntityInTextBotCommand && !parseBotCommands) ||
((type == EntityInTextBold || type == EntityInTextItalic || type == EntityInTextCode || type == EntityInTextPre) && !parseMono)) {
continue;
}
entities.push_back(preparsed.at(i));
@ -604,8 +597,13 @@ public:
start = src.constData();
end = start + src.size();
entitiesEnd = entities.cend();
waitingEntity = entities.cbegin();
while (waitingEntity != entitiesEnd && waitingEntity->length() <= 0) ++waitingEntity;
int firstMonospaceOffset = EntityInText::firstMonospaceOffset(entities, end - start);
ptr = start;
while (ptr != end && chIsTrimmed(*ptr, rich)) {
while (ptr != end && chIsTrimmed(*ptr, rich) && ptr != start + firstMonospaceOffset) {
++ptr;
}
while (ptr != end && chIsTrimmed(*(end - 1), rich)) {
@ -624,15 +622,8 @@ public:
ch = emojiLookback = 0;
lastSkipped = false;
checkTilde = !cRetina() && _t->_font->size() == 13 && _t->_font->flags() == 0; // tilde Open Sans fix
entitiesEnd = entities.cend();
waitingEntity = entities.cbegin();
while (waitingEntity != entitiesEnd && waitingEntity->length <= 0) ++waitingEntity;
for (; ptr <= end; ++ptr) {
checkEntities();
if (rich) {
if (checkCommand()) {
checkEntities();
}
while (checkEntities() || (rich && checkCommand())) {
}
parseCurrentChar();
parseEmojiFromCurrent();
@ -656,32 +647,44 @@ public:
lnkIndex = maxLnkIndex + (b->lnkIndex() - 0x8000);
if (_t->_links.size() < lnkIndex) {
_t->_links.resize(lnkIndex);
const TextLinkData &data(links[lnkIndex - maxLnkIndex - 1]);
ClickHandlerPtr lnk;
if (data.fullDisplayed < -4) { // hidden link
lnk.reset(new HiddenUrlClickHandler(data.url));
} else if (data.fullDisplayed < -3) { // bot command
lnk.reset(new BotCommandClickHandler(data.url));
} else if (data.fullDisplayed < -2) { // mention
const TextLinkData &link(links[lnkIndex - maxLnkIndex - 1]);
ClickHandlerPtr handler;
switch (link.type) {
case EntityInTextCustomUrl: handler.reset(new HiddenUrlClickHandler(link.data)); break;
case EntityInTextEmail:
case EntityInTextUrl: handler.reset(new UrlClickHandler(link.data, link.displayStatus == LinkDisplayedFull)); break;
case EntityInTextBotCommand: handler.reset(new BotCommandClickHandler(link.data)); break;
case EntityInTextHashtag:
if (options.flags & TextTwitterMentions) {
lnk.reset(new UrlClickHandler(qsl("https://twitter.com/") + data.url.mid(1), true));
handler.reset(new UrlClickHandler(qsl("https://twitter.com/hashtag/") + link.data.mid(1) + qsl("?src=hash"), true));
} else if (options.flags & TextInstagramMentions) {
lnk.reset(new UrlClickHandler(qsl("https://instagram.com/") + data.url.mid(1) + '/', true));
handler.reset(new UrlClickHandler(qsl("https://instagram.com/explore/tags/") + link.data.mid(1) + '/', true));
} else {
lnk.reset(new MentionClickHandler(data.url));
handler.reset(new HashtagClickHandler(link.data));
}
} else if (data.fullDisplayed < -1) { // hashtag
break;
case EntityInTextMention:
if (options.flags & TextTwitterMentions) {
lnk.reset(new UrlClickHandler(qsl("https://twitter.com/hashtag/") + data.url.mid(1) + qsl("?src=hash"), true));
handler.reset(new UrlClickHandler(qsl("https://twitter.com/") + link.data.mid(1), true));
} else if (options.flags & TextInstagramMentions) {
lnk.reset(new UrlClickHandler(qsl("https://instagram.com/explore/tags/") + data.url.mid(1) + '/', true));
handler.reset(new UrlClickHandler(qsl("https://instagram.com/") + link.data.mid(1) + '/', true));
} else {
lnk.reset(new HashtagClickHandler(data.url));
handler.reset(new MentionClickHandler(link.data));
}
} else { // email or url
lnk.reset(new UrlClickHandler(data.url, data.fullDisplayed != 0));
break;
case EntityInTextMentionName: {
UserId userId = 0;
uint64 accessHash = 0;
if (mentionNameToFields(link.data, &userId, &accessHash)) {
handler.reset(new MentionNameClickHandler(link.text, userId, accessHash));
} else {
LOG(("Bad mention name: %1").arg(link.data));
}
_t->setLink(lnkIndex, lnk);
} break;
}
t_assert(!handler.isNull());
_t->setLink(lnkIndex, handler);
}
b->setLnkIndex(lnkIndex);
}
@ -693,6 +696,30 @@ public:
private:
enum LinkDisplayStatus {
LinkDisplayedFull,
LinkDisplayedElided,
};
struct TextLinkData {
TextLinkData() = default;
TextLinkData(EntityInTextType type, const QString &text, const QString &data, LinkDisplayStatus displayStatus)
: type(type)
, text(text)
, data(data)
, displayStatus(displayStatus) {
}
EntityInTextType type = EntityInTextInvalid;
QString text, data;
LinkDisplayStatus displayStatus = LinkDisplayedFull;
};
void computeLinkText(const QString &linkData, QString *outLinkText, LinkDisplayStatus *outDisplayStatus) {
QUrl url(linkData), good(url.isValid() ? url.toEncoded() : "");
QString readable = good.isValid() ? good.toDisplayString() : linkData;
*outLinkText = _t->_font->elided(readable, st::linkCropLimit);
*outDisplayStatus = (*outLinkText == readable) ? LinkDisplayedFull : LinkDisplayedElided;
}
Text *_t;
QString src;
const QChar *start, *end, *ptr;
@ -701,12 +728,6 @@ private:
EntitiesInText entities;
EntitiesInText::const_iterator waitingEntity, entitiesEnd;
struct TextLinkData {
TextLinkData(const QString &url = QString(), int32 fullDisplayed = 1) : url(url), fullDisplayed(fullDisplayed) {
}
QString url;
int32 fullDisplayed; // -5 - custom text link, -4 - bot command, -3 - mention, -2 - hashtag, -1 - email
};
typedef QVector<TextLinkData> TextLinks;
TextLinks links;
@ -2477,7 +2498,7 @@ void Text::recountNaturalSize(bool initial, Qt::LayoutDirection optionsDir) {
}
}
void Text::setMarkedText(style::font font, const QString &text, const EntitiesInText &entities, const TextParseOptions &options) {
void Text::setMarkedText(style::font font, const TextWithEntities &textWithEntities, const TextParseOptions &options) {
if (!_textStyle) initDefault();
_font = font;
clear();
@ -2512,7 +2533,7 @@ void Text::setMarkedText(style::font font, const QString &text, const EntitiesIn
// newText.append("\n\n").append(text);
// TextParser parser(this, newText, EntitiesInText(), options);
TextParser parser(this, text, entities, options);
TextParser parser(this, textWithEntities, options);
}
recountNaturalSize(true, options.dir);
}
@ -2912,148 +2933,133 @@ TextSelection Text::adjustSelection(TextSelection selection, TextSelectType sele
return { from, to };
}
QString Text::original(TextSelection selection, ExpandLinksMode mode) const {
template <typename AppendPartCallback, typename ClickHandlerStartCallback, typename ClickHandlerFinishCallback, typename FlagsChangeCallback>
void Text::enumerateText(TextSelection selection, AppendPartCallback appendPartCallback, ClickHandlerStartCallback clickHandlerStartCallback, ClickHandlerFinishCallback clickHandlerFinishCallback, FlagsChangeCallback flagsChangeCallback) const {
if (isEmpty() || selection.empty()) {
return QString();
return;
}
QString result, emptyurl;
result.reserve(_text.size());
int32 lnkFrom = 0, lnkIndex = 0;
for (TextBlocks::const_iterator i = _blocks.cbegin(), e = _blocks.cend(); true; ++i) {
int32 blockLnkIndex = (i == e) ? 0 : (*i)->lnkIndex();
int32 blockFrom = (i == e) ? _text.size() : (*i)->from();
if (blockLnkIndex != lnkIndex) {
if (lnkIndex) { // write link
const ClickHandlerPtr &lnk(_links.at(lnkIndex - 1));
const QString &url = (mode == ExpandLinksNone || !lnk) ? emptyurl : lnk->text();
int32 rangeFrom = qMax(int32(selection.from), lnkFrom), rangeTo = qMin(blockFrom, int32(selection.to));
if (rangeTo > rangeFrom) {
QStringRef r = _text.midRef(rangeFrom, rangeTo - rangeFrom);
if (url.isEmpty() || lnkFrom != rangeFrom || blockFrom != rangeTo) {
result += r;
} else {
QUrl u(url);
QString displayed = (u.isValid() ? u.toDisplayString() : url);
bool shortened = (r.size() > 3) && (_text.midRef(lnkFrom, r.size() - 3) == displayed.midRef(0, r.size() - 3));
bool same = (r == displayed.midRef(0, r.size())) || (r == url.midRef(0, r.size()));
if (same || shortened) {
result += url;
} else if (mode == ExpandLinksAll) {
result.append(r).append(qsl(" ( ")).append(url).append(qsl(" )"));
} else {
result += r;
}
}
}
}
lnkIndex = blockLnkIndex;
lnkFrom = blockFrom;
}
if (i == e) break;
TextBlockType type = (*i)->type();
if (type == TextBlockTSkip) continue;
if (!blockLnkIndex) {
int32 rangeFrom = qMax(selection.from, (*i)->from()), rangeTo = qMin(selection.to, uint16((*i)->from() + TextPainter::_blockLength(this, i, e)));
if (rangeTo > rangeFrom) {
result += _text.midRef(rangeFrom, rangeTo - rangeFrom);
}
}
}
return result;
}
EntitiesInText Text::originalEntities() const {
EntitiesInText result;
QString emptyurl;
int32 originalLength = 0, lnkStart = 0, italicStart = 0, boldStart = 0, codeStart = 0, preStart = 0;
int32 lnkFrom = 0, lnkIndex = 0, flags = 0;
for (TextBlocks::const_iterator i = _blocks.cbegin(), e = _blocks.cend(); true; ++i) {
int32 blockLnkIndex = (i == e) ? 0 : (*i)->lnkIndex();
int32 blockFrom = (i == e) ? _text.size() : (*i)->from();
int lnkIndex = 0;
uint16 lnkFrom = 0;
int32 flags = 0;
for (auto i = _blocks.cbegin(), e = _blocks.cend(); true; ++i) {
int blockLnkIndex = (i == e) ? 0 : (*i)->lnkIndex();
uint16 blockFrom = (i == e) ? _text.size() : (*i)->from();
int32 blockFlags = (i == e) ? 0 : (*i)->flags();
if (blockFlags != flags) {
if ((flags & TextBlockFItalic) && !(blockFlags & TextBlockFItalic)) { // write italic
result.push_back(EntityInText(EntityInTextItalic, italicStart, originalLength - italicStart));
} else if ((blockFlags & TextBlockFItalic) && !(flags & TextBlockFItalic)) {
italicStart = originalLength;
}
if ((flags & TextBlockFSemibold) && !(blockFlags & TextBlockFSemibold)) {
result.push_back(EntityInText(EntityInTextBold, boldStart, originalLength - boldStart));
} else if ((blockFlags & TextBlockFSemibold) && !(flags & TextBlockFSemibold)) {
boldStart = originalLength;
}
if ((flags & TextBlockFCode) && !(blockFlags & TextBlockFCode)) {
result.push_back(EntityInText(EntityInTextCode, codeStart, originalLength - codeStart));
} else if ((blockFlags & TextBlockFCode) && !(flags & TextBlockFCode)) {
codeStart = originalLength;
}
if ((flags & TextBlockFPre) && !(blockFlags & TextBlockFPre)) {
result.push_back(EntityInText(EntityInTextPre, preStart, originalLength - preStart));
} else if ((blockFlags & TextBlockFPre) && !(flags & TextBlockFPre)) {
preStart = originalLength;
}
bool checkBlockFlags = (blockFrom >= selection.from) && (blockFrom <= selection.to);
if (checkBlockFlags && blockFlags != flags) {
flagsChangeCallback(flags, blockFlags);
flags = blockFlags;
}
if (blockLnkIndex && !_links.at(blockLnkIndex - 1)) { // ignore empty links
blockLnkIndex = 0;
}
if (blockLnkIndex != lnkIndex) {
if (lnkIndex) { // write link
const ClickHandlerPtr &lnk(_links.at(lnkIndex - 1));
const QString &url(lnk ? lnk->text() : emptyurl);
int32 rangeFrom = lnkFrom, rangeTo = blockFrom;
if (rangeTo > rangeFrom) {
if (lnkIndex) {
auto rangeFrom = qMax(selection.from, lnkFrom);
auto rangeTo = qMin(blockFrom, selection.to);
if (rangeTo > rangeFrom) { // handle click handler
QStringRef r = _text.midRef(rangeFrom, rangeTo - rangeFrom);
if (url.isEmpty()) {
originalLength += r.size();
if (lnkFrom != rangeFrom || blockFrom != rangeTo) {
appendPartCallback(r);
} else {
QUrl u(url);
QString displayed = (u.isValid() ? u.toDisplayString() : url);
bool shortened = (r.size() > 3) && (_text.midRef(lnkFrom, r.size() - 3) == displayed.midRef(0, r.size() - 3));
bool same = (r == displayed.midRef(0, r.size())) || (r == url.midRef(0, r.size()));
if (same || shortened) {
originalLength += url.size();
if (url.at(0) == '@') {
result.push_back(EntityInText(EntityInTextMention, lnkStart, originalLength - lnkStart));
} else if (url.at(0) == '#') {
result.push_back(EntityInText(EntityInTextHashtag, lnkStart, originalLength - lnkStart));
} else if (url.at(0) == '/') {
result.push_back(EntityInText(EntityInTextBotCommand, lnkStart, originalLength - lnkStart));
} else if (url.indexOf('@') > 0 && url.indexOf('/') <= 0) {
result.push_back(EntityInText(EntityInTextEmail, lnkStart, originalLength - lnkStart));
} else {
result.push_back(EntityInText(EntityInTextUrl, lnkStart, originalLength - lnkStart));
}
} else {
originalLength += r.size();
result.push_back(EntityInText(EntityInTextCustomUrl, lnkStart, originalLength - lnkStart, url));
}
clickHandlerFinishCallback(r, _links.at(lnkIndex - 1));
}
}
}
lnkIndex = blockLnkIndex;
if (lnkIndex) {
lnkFrom = blockFrom;
lnkStart = originalLength;
clickHandlerStartCallback();
}
}
if (i == e) break;
if (i == e || blockFrom >= selection.to) break;
TextBlockType type = (*i)->type();
if (type == TextBlockTSkip) continue;
if ((*i)->type() == TextBlockTSkip) continue;
if (!blockLnkIndex) {
int32 rangeFrom = (*i)->from(), rangeTo = uint16((*i)->from() + TextPainter::_blockLength(this, i, e));
auto rangeFrom = qMax(selection.from, blockFrom);
auto rangeTo = qMin(selection.to, uint16(blockFrom + TextPainter::_blockLength(this, i, e)));
if (rangeTo > rangeFrom) {
originalLength += rangeTo - rangeFrom;
appendPartCallback(_text.midRef(rangeFrom, rangeTo - rangeFrom));
}
}
}
}
TextWithEntities Text::originalTextWithEntities(TextSelection selection, ExpandLinksMode mode) const {
TextWithEntities result;
result.text.reserve(_text.size());
int lnkStart = 0, italicStart = 0, boldStart = 0, codeStart = 0, preStart = 0;
auto flagsChangeCallback = [&result, &italicStart, &boldStart, &codeStart, &preStart](int32 oldFlags, int32 newFlags) {
if ((oldFlags & TextBlockFItalic) && !(newFlags & TextBlockFItalic)) { // write italic
result.entities.push_back(EntityInText(EntityInTextItalic, italicStart, result.text.size() - italicStart));
} else if ((newFlags & TextBlockFItalic) && !(oldFlags & TextBlockFItalic)) {
italicStart = result.text.size();
}
if ((oldFlags & TextBlockFSemibold) && !(newFlags & TextBlockFSemibold)) {
result.entities.push_back(EntityInText(EntityInTextBold, boldStart, result.text.size() - boldStart));
} else if ((newFlags & TextBlockFSemibold) && !(oldFlags & TextBlockFSemibold)) {
boldStart = result.text.size();
}
if ((oldFlags & TextBlockFCode) && !(newFlags & TextBlockFCode)) {
result.entities.push_back(EntityInText(EntityInTextCode, codeStart, result.text.size() - codeStart));
} else if ((newFlags & TextBlockFCode) && !(oldFlags & TextBlockFCode)) {
codeStart = result.text.size();
}
if ((oldFlags & TextBlockFPre) && !(newFlags & TextBlockFPre)) {
result.entities.push_back(EntityInText(EntityInTextPre, preStart, result.text.size() - preStart));
} else if ((newFlags & TextBlockFPre) && !(oldFlags & TextBlockFPre)) {
preStart = result.text.size();
}
};
auto clickHandlerStartCallback = [&result, &lnkStart]() {
lnkStart = result.text.size();
};
auto clickHandlerFinishCallback = [mode, &result, &lnkStart](const QStringRef &r, const ClickHandlerPtr &handler) {
auto expanded = handler->getExpandedLinkTextWithEntities(mode, lnkStart, r);
if (expanded.text.isEmpty()) {
result.text += r;
} else {
result.text += expanded.text;
}
if (!expanded.entities.isEmpty()) {
result.entities.append(expanded.entities);
}
};
auto appendPartCallback = [&result](const QStringRef &r) {
result.text += r;
};
enumerateText(selection, appendPartCallback, clickHandlerStartCallback, clickHandlerFinishCallback, flagsChangeCallback);
return result;
}
QString Text::originalText(TextSelection selection, ExpandLinksMode mode) const {
QString result;
result.reserve(_text.size());
auto appendPartCallback = [&result](const QStringRef &r) {
result += r;
};
auto clickHandlerStartCallback = []() {
};
auto clickHandlerFinishCallback = [mode, &result](const QStringRef &r, const ClickHandlerPtr &handler) {
auto expanded = handler->getExpandedLinkText(mode, r);
if (expanded.isEmpty()) {
result += r;
} else {
result += expanded;
}
};
auto flagsChangeCallback = [](int32 oldFlags, int32 newFlags) {
};
enumerateText(selection, appendPartCallback, clickHandlerStartCallback, clickHandlerFinishCallback, flagsChangeCallback);
return result;
}

View file

@ -97,7 +97,7 @@ public:
int32 countHeight(int32 width) const;
void setText(style::font font, const QString &text, const TextParseOptions &options = _defaultOptions);
void setRichText(style::font font, const QString &text, TextParseOptions options = _defaultOptions, const TextCustomTagsMap &custom = TextCustomTagsMap());
void setMarkedText(style::font font, const QString &text, const EntitiesInText &entities, const TextParseOptions &options = _defaultOptions);
void setMarkedText(style::font font, const TextWithEntities &textWithEntities, const TextParseOptions &options = _defaultOptions);
void setLink(uint16 lnkIndex, const ClickHandlerPtr &lnk);
bool hasLinks() const;
@ -178,13 +178,9 @@ public:
int length() const {
return _text.size();
}
enum ExpandLinksMode {
ExpandLinksNone,
ExpandLinksShortened,
ExpandLinksAll,
};
QString original(TextSelection selection = AllTextSelection, ExpandLinksMode mode = ExpandLinksShortened) const;
EntitiesInText originalEntities() const;
TextWithEntities originalTextWithEntities(TextSelection selection = AllTextSelection, ExpandLinksMode mode = ExpandLinksShortened) const;
QString originalText(TextSelection selection = AllTextSelection, ExpandLinksMode mode = ExpandLinksShortened) const;
bool lastDots(int32 dots, int32 maxdots = 3) { // hack for typing animation
if (_text.size() < maxdots) return false;
@ -212,6 +208,10 @@ public:
private:
// Template method for originalText(), originalTextWithEntities().
template <typename AppendPartCallback, typename ClickHandlerStartCallback, typename ClickHandlerFinishCallback, typename FlagsChangeCallback>
void enumerateText(TextSelection selection, AppendPartCallback appendPartCallback, ClickHandlerStartCallback clickHandlerStartCallback, ClickHandlerFinishCallback clickHandlerFinishCallback, FlagsChangeCallback flagsChangeCallback) const;
void recountNaturalSize(bool initial, Qt::LayoutDirection optionsDir = Qt::LayoutDirectionAuto);
// clear() deletes all blocks and calls this method

View file

@ -1211,7 +1211,7 @@ bool textSplit(QString &sendingText, EntitiesInText &sendingEntities, QString &l
int32 s = 0, half = limit / 2, goodLevel = 0;
for (const QChar *start = leftText.constData(), *ch = start, *end = leftText.constEnd(), *good = ch; ch != end; ++ch, ++s) {
while (currentEntity < entityCount && ch >= start + leftEntities[currentEntity].offset + leftEntities[currentEntity].length) {
while (currentEntity < entityCount && ch >= start + leftEntities.at(currentEntity).offset() + leftEntities.at(currentEntity).length()) {
++currentEntity;
}
@ -1225,8 +1225,8 @@ goodCanBreakEntity = canBreakEntity;\
}
if (s > half) {
bool inEntity = (currentEntity < entityCount) && (ch > start + leftEntities[currentEntity].offset) && (ch < start + leftEntities[currentEntity].offset + leftEntities[currentEntity].length);
EntityInTextType entityType = (currentEntity < entityCount) ? leftEntities[currentEntity].type : EntityInTextBold;
bool inEntity = (currentEntity < entityCount) && (ch > start + leftEntities.at(currentEntity).offset()) && (ch < start + leftEntities.at(currentEntity).offset() + leftEntities.at(currentEntity).length());
EntityInTextType entityType = (currentEntity < entityCount) ? leftEntities.at(currentEntity).type() : EntityInTextInvalid;
bool canBreakEntity = (entityType == EntityInTextPre || entityType == EntityInTextCode);
int32 noEntityLevel = inEntity ? 0 : 1;
if (inEntity && !canBreakEntity) {
@ -1241,9 +1241,9 @@ goodCanBreakEntity = canBreakEntity;\
}
} else if (ch + 1 < end && chIsNewline(*(ch + 1))) {
MARK_GOOD_AS_LEVEL(15);
} else if (currentEntity < entityCount && ch + 1 == start + leftEntities[currentEntity].offset && leftEntities[currentEntity].type == EntityInTextPre) {
} else if (currentEntity < entityCount && ch + 1 == start + leftEntities.at(currentEntity).offset() && leftEntities.at(currentEntity).type() == EntityInTextPre) {
MARK_GOOD_AS_LEVEL(14);
} else if (currentEntity > 0 && ch == start + leftEntities[currentEntity - 1].offset + leftEntities[currentEntity - 1].length && leftEntities[currentEntity - 1].type == EntityInTextPre) {
} else if (currentEntity > 0 && ch == start + leftEntities.at(currentEntity - 1).offset() + leftEntities.at(currentEntity - 1).length() && leftEntities.at(currentEntity - 1).type() == EntityInTextPre) {
MARK_GOOD_AS_LEVEL(14);
} else {
MARK_GOOD_AS_LEVEL(13);
@ -1285,14 +1285,10 @@ goodCanBreakEntity = canBreakEntity;\
if (goodInEntity) {
if (goodCanBreakEntity) {
sendingEntities = leftEntities.mid(0, goodEntity + 1);
sendingEntities.back().length = good - start - sendingEntities.back().offset;
sendingEntities.back().updateTextEnd(good - start);
leftEntities = leftEntities.mid(goodEntity);
for (EntitiesInText::iterator i = leftEntities.begin(), e = leftEntities.end(); i != e; ++i) {
i->offset -= good - start;
if (i->offset < 0) {
i->length += i->offset;
i->offset = 0;
}
for (auto &entity : leftEntities) {
entity.shiftLeft(good - start);
}
} else {
sendingEntities = leftEntities.mid(0, goodEntity);
@ -1301,8 +1297,8 @@ goodCanBreakEntity = canBreakEntity;\
} else {
sendingEntities = leftEntities.mid(0, goodEntity);
leftEntities = leftEntities.mid(goodEntity);
for (EntitiesInText::iterator i = leftEntities.begin(), e = leftEntities.end(); i != e; ++i) {
i->offset -= good - start;
for (auto &entity : leftEntities) {
entity.shiftLeft(good - start);
}
}
return true;
@ -1367,6 +1363,29 @@ EntitiesInText entitiesFromMTP(const QVector<MTPMessageEntity> &entities) {
case mtpc_messageEntityEmail: { const auto &d(entity.c_messageEntityEmail()); result.push_back(EntityInText(EntityInTextEmail, d.voffset.v, d.vlength.v)); } break;
case mtpc_messageEntityHashtag: { const auto &d(entity.c_messageEntityHashtag()); result.push_back(EntityInText(EntityInTextHashtag, d.voffset.v, d.vlength.v)); } break;
case mtpc_messageEntityMention: { const auto &d(entity.c_messageEntityMention()); result.push_back(EntityInText(EntityInTextMention, d.voffset.v, d.vlength.v)); } break;
case mtpc_messageEntityMentionName: {
const auto &d(entity.c_messageEntityMentionName());
auto data = QString::number(d.vuser_id.v);
if (auto user = App::userLoaded(peerFromUser(d.vuser_id))) {
data += '.' + QString::number(user->access);
}
result.push_back(EntityInText(EntityInTextMentionName, d.voffset.v, d.vlength.v, data));
} break;
case mtpc_inputMessageEntityMentionName: {
const auto &d(entity.c_inputMessageEntityMentionName());
auto data = ([&d]() -> QString {
if (d.vuser_id.type() == mtpc_inputUserSelf) {
return QString::number(MTP::authedId());
} else if (d.vuser_id.type() == mtpc_inputUser) {
const auto &user(d.vuser_id.c_inputUser());
return QString::number(user.vuser_id.v) + '.' + QString::number(user.vaccess_hash.v);
}
return QString();
})();
if (!data.isEmpty()) {
result.push_back(EntityInText(EntityInTextMentionName, d.voffset.v, d.vlength.v, data));
}
} break;
case mtpc_messageEntityBotCommand: { const auto &d(entity.c_messageEntityBotCommand()); result.push_back(EntityInText(EntityInTextBotCommand, d.voffset.v, d.vlength.v)); } break;
case mtpc_messageEntityBold: { const auto &d(entity.c_messageEntityBold()); result.push_back(EntityInText(EntityInTextBold, d.voffset.v, d.vlength.v)); } break;
case mtpc_messageEntityItalic: { const auto &d(entity.c_messageEntityItalic()); result.push_back(EntityInText(EntityInTextItalic, d.voffset.v, d.vlength.v)); } break;
@ -1382,28 +1401,50 @@ MTPVector<MTPMessageEntity> linksToMTP(const EntitiesInText &links, bool sending
MTPVector<MTPMessageEntity> result(MTP_vector<MTPMessageEntity>(0));
auto &v = result._vector().v;
for_const (const auto &link, links) {
if (link.length <= 0) continue;
if (sending && link.type != EntityInTextCode && link.type != EntityInTextPre) continue;
if (link.length() <= 0) continue;
if (sending
&& link.type() != EntityInTextCode
&& link.type() != EntityInTextPre
&& link.type() != EntityInTextMentionName) {
continue;
}
auto offset = MTP_int(link.offset), length = MTP_int(link.length);
switch (link.type) {
auto offset = MTP_int(link.offset()), length = MTP_int(link.length());
switch (link.type()) {
case EntityInTextUrl: v.push_back(MTP_messageEntityUrl(offset, length)); break;
case EntityInTextCustomUrl: v.push_back(MTP_messageEntityTextUrl(offset, length, MTP_string(link.text))); break;
case EntityInTextCustomUrl: v.push_back(MTP_messageEntityTextUrl(offset, length, MTP_string(link.data()))); break;
case EntityInTextEmail: v.push_back(MTP_messageEntityEmail(offset, length)); break;
case EntityInTextHashtag: v.push_back(MTP_messageEntityHashtag(offset, length)); break;
case EntityInTextMention: v.push_back(MTP_messageEntityMention(offset, length)); break;
case EntityInTextMentionName: {
auto inputUser = ([](const QString &data) -> MTPInputUser {
UserId userId = 0;
uint64 accessHash = 0;
if (mentionNameToFields(data, &userId, &accessHash)) {
if (userId == MTP::authedId()) {
return MTP_inputUserSelf();
}
return MTP_inputUser(MTP_int(userId), MTP_long(accessHash));
}
return MTP_inputUserEmpty();
})(link.data());
if (inputUser.type() != mtpc_inputUserEmpty) {
v.push_back(MTP_inputMessageEntityMentionName(offset, length, inputUser));
}
} break;
case EntityInTextBotCommand: v.push_back(MTP_messageEntityBotCommand(offset, length)); break;
case EntityInTextBold: v.push_back(MTP_messageEntityBold(offset, length)); break;
case EntityInTextItalic: v.push_back(MTP_messageEntityItalic(offset, length)); break;
case EntityInTextCode: v.push_back(MTP_messageEntityCode(offset, length)); break;
case EntityInTextPre: v.push_back(MTP_messageEntityPre(offset, length, MTP_string(link.text))); break;
case EntityInTextPre: v.push_back(MTP_messageEntityPre(offset, length, MTP_string(link.data()))); break;
}
}
return result;
}
EntitiesInText textParseEntities(QString &text, int32 flags, bool rich) { // some code is duplicated in flattextarea.cpp!
EntitiesInText result, mono;
// Some code is duplicated in flattextarea.cpp!
void textParseEntities(QString &text, int32 flags, EntitiesInText *inOutEntities, bool rich) {
EntitiesInText result;
bool withHashtags = (flags & TextParseHashtags);
bool withMentions = (flags & TextParseMentions);
@ -1411,6 +1452,9 @@ EntitiesInText textParseEntities(QString &text, int32 flags, bool rich) { // som
bool withMono = (flags & TextParseMono);
if (withMono) { // parse mono entities (code and pre)
int existingEntityIndex = 0, existingEntitiesCount = inOutEntities->size();
int existingEntityShiftLeft = 0;
QString newText;
int32 offset = 0, matchOffset = offset, len = text.size(), commandOffset = rich ? 0 : len;
@ -1434,7 +1478,7 @@ EntitiesInText textParseEntities(QString &text, int32 flags, bool rich) { // som
auto mCode = _reCode.match(text, matchOffset);
if (!mPre.hasMatch() && !mCode.hasMatch()) break;
int32 preStart = mPre.hasMatch() ? mPre.capturedStart() : INT_MAX,
int preStart = mPre.hasMatch() ? mPre.capturedStart() : INT_MAX,
preEnd = mPre.hasMatch() ? mPre.capturedEnd() : INT_MAX,
codeStart = mCode.hasMatch() ? mCode.capturedStart() : INT_MAX,
codeEnd = mCode.hasMatch() ? mCode.capturedEnd() : INT_MAX,
@ -1472,11 +1516,25 @@ EntitiesInText textParseEntities(QString &text, int32 flags, bool rich) { // som
continue;
}
if (newText.isEmpty()) newText.reserve(text.size());
bool addNewlineBefore = false, addNewlineAfter = false;
int32 outerStart = tagStart, outerEnd = tagEnd;
int32 innerStart = tagStart + mTag.capturedLength(2), innerEnd = tagEnd - mTag.capturedLength(3);
// Check if start or end sequences intersect any existing entity.
int intersectedEntityEnd = 0;
for_const (auto &entity, *inOutEntities) {
if (qMin(innerStart, entity.offset() + entity.length()) > qMax(outerStart, entity.offset()) ||
qMin(outerEnd, entity.offset() + entity.length()) > qMax(innerEnd, entity.offset())) {
intersectedEntityEnd = entity.offset() + entity.length();
break;
}
}
if (intersectedEntityEnd > 0) {
matchOffset = qMax(innerStart, intersectedEntityEnd);
continue;
}
if (newText.isEmpty()) newText.reserve(text.size());
if (pre) {
while (outerStart > 0 && chIsSpace(*(start + outerStart - 1), rich) && !chIsNewline(*(start + outerStart - 1))) {
--outerStart;
@ -1506,14 +1564,27 @@ EntitiesInText textParseEntities(QString &text, int32 flags, bool rich) { // som
}
addNewlineAfter = (outerEnd < len && !chIsNewline(*(start + outerEnd)));
}
for (; existingEntityIndex < existingEntitiesCount && inOutEntities->at(existingEntityIndex).offset() < innerStart; ++existingEntityIndex) {
auto &entity = inOutEntities->at(existingEntityIndex);
result.push_back(entity);
result.back().shiftLeft(existingEntityShiftLeft);
}
if (outerStart > offset) newText.append(start + offset, outerStart - offset);
if (addNewlineBefore) newText.append('\n');
existingEntityShiftLeft += (innerStart - outerStart) - (addNewlineBefore ? 1 : 0);
int32 tagLength = innerEnd - innerStart;
mono.push_back(EntityInText(pre ? EntityInTextPre : EntityInTextCode, newText.size(), tagLength));
int entityStart = newText.size(), entityLength = innerEnd - innerStart;
result.push_back(EntityInText(pre ? EntityInTextPre : EntityInTextCode, entityStart, entityLength));
newText.append(start + innerStart, tagLength);
for (; existingEntityIndex < existingEntitiesCount && inOutEntities->at(existingEntityIndex).offset() <= innerEnd; ++existingEntityIndex) {
auto &entity = inOutEntities->at(existingEntityIndex);
result.push_back(entity);
result.back().shiftLeft(existingEntityShiftLeft);
}
newText.append(start + innerStart, entityLength);
if (addNewlineAfter) newText.append('\n');
existingEntityShiftLeft += (outerEnd - innerEnd) - (addNewlineAfter ? 1 : 0);
offset = matchOffset = outerEnd;
}
@ -1521,8 +1592,19 @@ EntitiesInText textParseEntities(QString &text, int32 flags, bool rich) { // som
newText.append(start + offset, len - offset);
text = newText;
}
if (!result.isEmpty()) {
for (; existingEntityIndex < existingEntitiesCount; ++existingEntityIndex) {
auto &entity = inOutEntities->at(existingEntityIndex);
result.push_back(entity);
result.back().shiftLeft(existingEntityShiftLeft);
}
int32 monoEntity = 0, monoCount = mono.size(), monoTill = 0;
*inOutEntities = result;
result = EntitiesInText();
}
}
int existingEntityIndex = 0, existingEntitiesCount = inOutEntities->size();
int existingEntityEnd = 0;
initLinkSets();
int32 len = text.size(), commandOffset = rich ? 0 : len;
@ -1538,11 +1620,11 @@ EntitiesInText textParseEntities(QString &text, int32 flags, bool rich) { // som
}
}
}
QRegularExpressionMatch mDomain = _reDomain.match(text, matchOffset);
QRegularExpressionMatch mExplicitDomain = _reExplicitDomain.match(text, matchOffset);
QRegularExpressionMatch mHashtag = withHashtags ? _reHashtag.match(text, matchOffset) : QRegularExpressionMatch();
QRegularExpressionMatch mMention = withMentions ? _reMention.match(text, qMax(mentionSkip, matchOffset)) : QRegularExpressionMatch();
QRegularExpressionMatch mBotCommand = withBotCommands ? _reBotCommand.match(text, matchOffset) : QRegularExpressionMatch();
auto mDomain = _reDomain.match(text, matchOffset);
auto mExplicitDomain = _reExplicitDomain.match(text, matchOffset);
auto mHashtag = withHashtags ? _reHashtag.match(text, matchOffset) : QRegularExpressionMatch();
auto mMention = withMentions ? _reMention.match(text, qMax(mentionSkip, matchOffset)) : QRegularExpressionMatch();
auto mBotCommand = withBotCommands ? _reBotCommand.match(text, matchOffset) : QRegularExpressionMatch();
EntityInTextType lnkType = EntityInTextUrl;
int32 lnkStart = 0, lnkLength = 0;
@ -1701,22 +1783,24 @@ EntitiesInText textParseEntities(QString &text, int32 flags, bool rich) { // som
lnkLength = (p - start) - lnkStart;
}
}
for (; monoEntity < monoCount && mono[monoEntity].offset <= lnkStart; ++monoEntity) {
monoTill = qMax(monoTill, mono[monoEntity].offset + mono[monoEntity].length);
result.push_back(mono[monoEntity]);
for (; existingEntityIndex < existingEntitiesCount && inOutEntities->at(existingEntityIndex).offset() <= lnkStart; ++existingEntityIndex) {
auto &entity = inOutEntities->at(existingEntityIndex);
accumulate_max(existingEntityEnd, entity.offset() + entity.length());
result.push_back(entity);
}
if (lnkStart >= monoTill) {
result.push_back(EntityInText(lnkType, lnkStart, lnkLength));
if (lnkStart >= existingEntityEnd) {
inOutEntities->push_back(EntityInText(lnkType, lnkStart, lnkLength));
}
offset = matchOffset = lnkStart + lnkLength;
}
for (; monoEntity < monoCount; ++monoEntity) {
monoTill = qMax(monoTill, mono[monoEntity].offset + mono[monoEntity].length);
result.push_back(mono[monoEntity]);
if (!result.isEmpty()) {
for (; existingEntityIndex < existingEntitiesCount; ++existingEntityIndex) {
auto &entity = inOutEntities->at(existingEntityIndex);
result.push_back(entity);
}
*inOutEntities = result;
}
return result;
}
QString textApplyEntities(const QString &text, const EntitiesInText &entities) {
@ -1728,15 +1812,15 @@ QString textApplyEntities(const QString &text, const EntitiesInText &entities) {
QString result;
int32 size = text.size();
const QChar *b = text.constData(), *already = b, *e = b + size;
EntitiesInText::const_iterator entity = entities.cbegin(), end = entities.cend();
while (entity != end && ((entity->type != EntityInTextCode && entity->type != EntityInTextPre) || entity->length <= 0 || entity->offset >= size)) {
auto entity = entities.cbegin(), end = entities.cend();
while (entity != end && ((entity->type() != EntityInTextCode && entity->type() != EntityInTextPre) || entity->length() <= 0 || entity->offset() >= size)) {
++entity;
}
while (entity != end || !closingTags.isEmpty()) {
int32 nextOpenEntity = (entity == end) ? (size + 1) : entity->offset;
int32 nextOpenEntity = (entity == end) ? (size + 1) : entity->offset();
int32 nextCloseEntity = closingTags.isEmpty() ? (size + 1) : closingTags.cbegin().key();
if (nextOpenEntity <= nextCloseEntity) {
QString tag = (entity->type == EntityInTextCode) ? code : pre;
QString tag = (entity->type() == EntityInTextCode) ? code : pre;
if (result.isEmpty()) result.reserve(text.size() + entities.size() * pre.size() * 2);
const QChar *offset = b + nextOpenEntity;
@ -1745,10 +1829,10 @@ QString textApplyEntities(const QString &text, const EntitiesInText &entities) {
already = offset;
}
result.append(tag);
closingTags.insert(qMin(entity->offset + entity->length, size), tag);
closingTags.insert(qMin(entity->offset() + entity->length(), size), tag);
++entity;
while (entity != end && ((entity->type != EntityInTextCode && entity->type != EntityInTextPre) || entity->length <= 0 || entity->offset >= size)) {
while (entity != end && ((entity->type() != EntityInTextCode && entity->type() != EntityInTextPre) || entity->length() <= 0 || entity->offset() >= size)) {
++entity;
}
} else {
@ -1771,79 +1855,18 @@ QString textApplyEntities(const QString &text, const EntitiesInText &entities) {
return result;
}
void replaceStringWithEntities(const QLatin1String &from, QChar to, QString &result, EntitiesInText &entities, bool checkSpace = false) {
int32 len = from.size(), s = result.size(), offset = 0, length = 0;
EntitiesInText::iterator i = entities.begin(), e = entities.end();
for (QChar *start = result.data(); offset < s;) {
int32 nextOffset = result.indexOf(from, offset);
if (nextOffset < 0) {
moveStringPart(start, length, offset, s - offset, entities);
break;
}
if (checkSpace) {
bool spaceBefore = (nextOffset > 0) && (start + nextOffset - 1)->isSpace();
bool spaceAfter = (nextOffset + len < s) && (start + nextOffset + len)->isSpace();
if (!spaceBefore && !spaceAfter) {
moveStringPart(start, length, offset, nextOffset - offset + len + 1, entities);
continue;
}
}
bool skip = false;
for (; i != e; ++i) { // find and check next finishing entity
if (i->offset + i->length > nextOffset) {
skip = (i->offset < nextOffset + len);
break;
}
}
if (skip) {
moveStringPart(start, length, offset, nextOffset - offset + len, entities);
continue;
}
moveStringPart(start, length, offset, nextOffset - offset, entities);
*(start + length) = to;
++length;
offset += len;
}
if (length < s) result.resize(length);
}
QString prepareTextWithEntities(QString result, EntitiesInText &entities, int32 flags) {
cleanTextWithEntities(result, entities);
if (flags) {
entities = textParseEntities(result, flags);
}
replaceStringWithEntities(qstr("--"), QChar(8212), result, entities, true);
replaceStringWithEntities(qstr("<<"), QChar(171), result, entities);
replaceStringWithEntities(qstr(">>"), QChar(187), result, entities);
if (cReplaceEmojis()) {
result = replaceEmojis(result, entities);
}
trimTextWithEntities(result, entities);
return result;
}
void moveStringPart(QChar *start, int32 &to, int32 &from, int32 count, EntitiesInText &entities) {
void moveStringPart(QChar *start, int32 &to, int32 &from, int32 count, EntitiesInText *inOutEntities) {
if (count > 0) {
if (to < from) {
memmove(start + to, start + from, count * sizeof(QChar));
for (auto &entity : entities) {
if (entity.offset >= from + count) break;
if (entity.offset + entity.length < from) continue;
if (entity.offset >= from) {
entity.offset -= (from - to);
entity.length += (from - to);
for (auto &entity : *inOutEntities) {
if (entity.offset() >= from + count) break;
if (entity.offset() + entity.length() < from) continue;
if (entity.offset() >= from) {
entity.extendToLeft(from - to);
}
if (entity.offset + entity.length < from + count) {
entity.length -= (from - to);
if (entity.offset() + entity.length() < from + count) {
entity.shrinkFromRight(from - to);
}
}
}
@ -1852,64 +1875,116 @@ void moveStringPart(QChar *start, int32 &to, int32 &from, int32 count, EntitiesI
}
}
void replaceStringWithEntities(const QLatin1String &from, QChar to, QString &result, EntitiesInText *inOutEntities, bool checkSpace = false) {
int32 len = from.size(), s = result.size(), offset = 0, length = 0;
EntitiesInText::iterator i = inOutEntities->begin(), e = inOutEntities->end();
for (QChar *start = result.data(); offset < s;) {
int32 nextOffset = result.indexOf(from, offset);
if (nextOffset < 0) {
moveStringPart(start, length, offset, s - offset, inOutEntities);
break;
}
if (checkSpace) {
bool spaceBefore = (nextOffset > 0) && (start + nextOffset - 1)->isSpace();
bool spaceAfter = (nextOffset + len < s) && (start + nextOffset + len)->isSpace();
if (!spaceBefore && !spaceAfter) {
moveStringPart(start, length, offset, nextOffset - offset + len + 1, inOutEntities);
continue;
}
}
bool skip = false;
for (; i != e; ++i) { // find and check next finishing entity
if (i->offset() + i->length() > nextOffset) {
skip = (i->offset() < nextOffset + len);
break;
}
}
if (skip) {
moveStringPart(start, length, offset, nextOffset - offset + len, inOutEntities);
continue;
}
moveStringPart(start, length, offset, nextOffset - offset, inOutEntities);
*(start + length) = to;
++length;
offset += len;
}
if (length < s) result.resize(length);
}
QString prepareTextWithEntities(QString result, int32 flags, EntitiesInText *inOutEntities) {
cleanTextWithEntities(result, inOutEntities);
if (flags) {
textParseEntities(result, flags, inOutEntities);
}
replaceStringWithEntities(qstr("--"), QChar(8212), result, inOutEntities, true);
replaceStringWithEntities(qstr("<<"), QChar(171), result, inOutEntities);
replaceStringWithEntities(qstr(">>"), QChar(187), result, inOutEntities);
if (cReplaceEmojis()) {
result = replaceEmojis(result, inOutEntities);
}
trimTextWithEntities(result, inOutEntities);
return result;
}
// replace bad symbols with space and remove \r
void cleanTextWithEntities(QString &result, EntitiesInText &entities) {
void cleanTextWithEntities(QString &result, EntitiesInText *inOutEntities) {
result = result.replace('\t', qstr(" "));
int32 len = result.size(), to = 0, from = 0;
QChar *start = result.data();
for (QChar *ch = start, *end = start + len; ch < end; ++ch) {
if (ch->unicode() == '\r') {
moveStringPart(start, to, from, (ch - start) - from, entities);
moveStringPart(start, to, from, (ch - start) - from, inOutEntities);
++from;
} else if (chReplacedBySpace(*ch)) {
*ch = ' ';
}
}
moveStringPart(start, to, from, len - from, entities);
moveStringPart(start, to, from, len - from, inOutEntities);
if (to < len) result.resize(to);
}
void trimTextWithEntities(QString &result, EntitiesInText &entities) {
bool foundNotTrimmed = false;
for (QChar *s = result.data(), *e = s + result.size(), *ch = e; ch != s;) { // rtrim
void trimTextWithEntities(QString &result, EntitiesInText *inOutEntities) {
bool foundNotTrimmedChar = false;
// right trim
for (QChar *s = result.data(), *e = s + result.size(), *ch = e; ch != s;) {
--ch;
if (!chIsTrimmed(*ch)) {
if (ch + 1 < e) {
int32 l = ch + 1 - s;
for (EntitiesInText::iterator i = entities.begin(), e = entities.end(); i != e; ++i) {
if (i->offset > l) {
i->offset = l;
i->length = 0;
} else if (i->offset + i->length > l) {
i->length = l - i->offset;
}
for (auto &entity : *inOutEntities) {
entity.updateTextEnd(l);
}
result.resize(l);
}
foundNotTrimmed = true;
foundNotTrimmedChar = true;
break;
}
}
if (!foundNotTrimmed) {
if (!foundNotTrimmedChar) {
result.clear();
entities.clear();
inOutEntities->clear();
return;
}
for (QChar *s = result.data(), *ch = s, *e = s + result.size(); ch != e; ++ch) { // ltrim
if (!chIsTrimmed(*ch)) {
int firstMonospaceOffset = EntityInText::firstMonospaceOffset(*inOutEntities, result.size());
// left trim
for (QChar *s = result.data(), *ch = s, *e = s + result.size(); ch != e; ++ch) {
if (!chIsTrimmed(*ch) || (ch - s) == firstMonospaceOffset) {
if (ch > s) {
int32 l = ch - s;
for (EntitiesInText::iterator i = entities.begin(), e = entities.end(); i != e; ++i) {
if (i->offset + i->length <= l) {
i->length = 0;
i->offset = 0;
} else if (i->offset < l) {
i->length = i->offset + i->length - l;
i->offset = 0;
} else {
i->offset -= l;
}
for (auto &entity : *inOutEntities) {
entity.shiftLeft(l);
}
result = result.mid(l);
}

View file

@ -21,11 +21,14 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
#pragma once
enum EntityInTextType {
EntityInTextInvalid = 0,
EntityInTextUrl,
EntityInTextCustomUrl,
EntityInTextEmail,
EntityInTextHashtag,
EntityInTextMention,
EntityInTextMentionName,
EntityInTextBotCommand,
EntityInTextBold,
@ -33,14 +36,94 @@ enum EntityInTextType {
EntityInTextCode, // inline
EntityInTextPre, // block
};
struct EntityInText {
EntityInText(EntityInTextType type, int offset, int length, const QString &text = QString()) : type(type), offset(offset), length(length), text(text) {
class EntityInText;
using EntitiesInText = QList<EntityInText>;
class EntityInText {
public:
EntityInText(EntityInTextType type, int offset, int length, const QString &data = QString())
: _type(type)
, _offset(offset)
, _length(length)
, _data(data) {
}
EntityInTextType type;
int offset, length;
QString text;
EntityInTextType type() const {
return _type;
}
int offset() const {
return _offset;
}
int length() const {
return _length;
}
QString data() const {
return _data;
}
void extendToLeft(int extent) {
_offset -= extent;
_length += extent;
}
void shrinkFromRight(int shrink) {
_length -= shrink;
}
void shiftLeft(int shift) {
_offset -= shift;
if (_offset < 0) {
_length += _offset;
_offset = 0;
if (_length < 0) {
_length = 0;
}
}
}
void shiftRight(int shift) {
_offset += shift;
}
void updateTextEnd(int textEnd) {
if (_offset > textEnd) {
_offset = textEnd;
_length = 0;
} else if (_offset + _length > textEnd) {
_length = textEnd - _offset;
}
}
static int firstMonospaceOffset(const EntitiesInText &entities, int textLength) {
int result = textLength;
for_const (auto &entity, entities) {
if (entity.type() == EntityInTextPre || entity.type() == EntityInTextCode) {
accumulate_min(result, entity.offset());
}
}
return result;
}
explicit operator bool() const {
return type() != EntityInTextInvalid;
}
private:
EntityInTextType _type;
int _offset, _length;
QString _data;
};
typedef QList<EntityInText> EntitiesInText;
struct TextWithEntities {
QString text;
EntitiesInText entities;
};
inline void appendTextWithEntities(TextWithEntities &to, TextWithEntities &&append) {
int entitiesShiftRight = to.text.size();
for (auto &entity : append.entities) {
entity.shiftRight(entitiesShiftRight);
}
to.text += append.text;
to.entities += append.entities;
}
// text preprocess
QString textClean(const QString &text);
@ -65,21 +148,36 @@ enum {
TextInstagramHashtags = 0x800,
};
inline bool mentionNameToFields(const QString &data, int32 *outUserId, uint64 *outAccessHash) {
auto components = data.split('.');
if (!components.isEmpty()) {
*outUserId = components.at(0).toInt();
*outAccessHash = (components.size() > 1) ? components.at(1).toULongLong() : 0;
return (*outUserId != 0);
}
return false;
}
inline QString mentionNameFromFields(int32 userId, uint64 accessHash) {
return QString::number(userId) + '.' + QString::number(accessHash);
}
EntitiesInText entitiesFromMTP(const QVector<MTPMessageEntity> &entities);
MTPVector<MTPMessageEntity> linksToMTP(const EntitiesInText &links, bool sending = false);
EntitiesInText textParseEntities(QString &text, int32 flags, bool rich = false); // changes text if (flags & TextParseMono)
// New entities are added to the ones that are already in inOutEntities.
// Changes text if (flags & TextParseMono).
void textParseEntities(QString &text, int32 flags, EntitiesInText *inOutEntities, bool rich = false);
QString textApplyEntities(const QString &text, const EntitiesInText &entities);
QString prepareTextWithEntities(QString result, EntitiesInText &entities, int32 flags);
QString prepareTextWithEntities(QString result, int32 flags, EntitiesInText *inOutEntities);
inline QString prepareText(QString result, bool checkLinks = false) {
EntitiesInText entities;
return prepareTextWithEntities(result, entities, checkLinks ? (TextParseLinks | TextParseMentions | TextParseHashtags | TextParseBotCommands) : 0);
auto prepareFlags = checkLinks ? (TextParseLinks | TextParseMentions | TextParseHashtags | TextParseBotCommands) : 0;
return prepareTextWithEntities(result, prepareFlags, &entities);
}
void moveStringPart(QChar *start, int32 &to, int32 &from, int32 count, EntitiesInText &entities);
// replace bad symbols with space and remove \r
void cleanTextWithEntities(QString &result, EntitiesInText &entities);
void trimTextWithEntities(QString &result, EntitiesInText &entities);
void cleanTextWithEntities(QString &result, EntitiesInText *inOutEntities);
void trimTextWithEntities(QString &result, EntitiesInText *inOutEntities);

View file

@ -125,6 +125,7 @@ SOURCES += \
./SourceFiles/dialogs/dialogs_indexed_list.cpp \
./SourceFiles/dialogs/dialogs_layout.cpp \
./SourceFiles/dialogs/dialogs_list.cpp \
./SourceFiles/history/field_autocomplete.cpp \
./SourceFiles/inline_bots/inline_bot_layout_internal.cpp \
./SourceFiles/inline_bots/inline_bot_layout_item.cpp \
./SourceFiles/inline_bots/inline_bot_result.cpp \
@ -248,6 +249,7 @@ HEADERS += \
./SourceFiles/dialogs/dialogs_list.h \
./SourceFiles/dialogs/dialogs_row.h \
./SourceFiles/history/history_common.h \
./SourceFiles/history/field_autocomplete.h \
./SourceFiles/inline_bots/inline_bot_layout_internal.h \
./SourceFiles/inline_bots/inline_bot_layout_item.h \
./SourceFiles/inline_bots/inline_bot_result.h \

View file

@ -270,6 +270,10 @@
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Deploy|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="GeneratedFiles\Debug\moc_field_autocomplete.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Deploy|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="GeneratedFiles\Debug\moc_fileuploader.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Deploy|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
@ -573,6 +577,10 @@
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="GeneratedFiles\Deploy\moc_field_autocomplete.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="GeneratedFiles\Deploy\moc_fileuploader.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
@ -902,6 +910,10 @@
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Deploy|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="GeneratedFiles\Release\moc_field_autocomplete.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Deploy|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="GeneratedFiles\Release\moc_fileuploader.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Deploy|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
@ -1135,6 +1147,7 @@
<ClCompile Include="SourceFiles\fileuploader.cpp" />
<ClCompile Include="SourceFiles\history.cpp" />
<ClCompile Include="SourceFiles\historywidget.cpp" />
<ClCompile Include="SourceFiles\history\field_autocomplete.cpp" />
<ClCompile Include="SourceFiles\inline_bots\inline_bot_layout_internal.cpp" />
<ClCompile Include="SourceFiles\inline_bots\inline_bot_layout_item.cpp" />
<ClCompile Include="SourceFiles\inline_bots\inline_bot_result.cpp" />
@ -1321,6 +1334,20 @@
<ClInclude Include="SourceFiles\dialogs\dialogs_layout.h" />
<ClInclude Include="SourceFiles\dialogs\dialogs_list.h" />
<ClInclude Include="SourceFiles\dialogs\dialogs_row.h" />
<CustomBuild Include="SourceFiles\history\field_autocomplete.h">
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Deploy|Win32'">$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs>
<Message Condition="'$(Configuration)|$(Platform)'=='Deploy|Win32'">Moc%27ing field_autocomplete.h...</Message>
<Outputs Condition="'$(Configuration)|$(Platform)'=='Deploy|Win32'">.\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp</Outputs>
<Command Condition="'$(Configuration)|$(Platform)'=='Deploy|Win32'">"$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fstdafx.h" "-f../../SourceFiles/history/field_autocomplete.h" -DAL_LIBTYPE_STATIC -DCUSTOM_API_ID -DUNICODE -DWIN32 -DWIN64 -DHAVE_STDINT_H -DZLIB_WINAPI -DQT_NO_DEBUG -DNDEBUG -D_SCL_SECURE_NO_WARNINGS "-I.\SourceFiles" "-I.\GeneratedFiles" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtCore\5.6.0\QtCore" "-I$(QTDIR)\include\QtGui\5.6.0\QtGui" "-I.\..\..\Libraries\breakpad\src" "-I.\..\..\Libraries\lzma\C" "-I.\..\..\Libraries\libexif-0.6.20" "-I.\..\..\Libraries\zlib-1.2.8" "-I.\..\..\Libraries\ffmpeg" "-I.\..\..\Libraries\openal-soft\include" "-I.\ThirdParty\minizip" "-I.\..\..\Libraries\openssl\Release\include"</Command>
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs>
<Message Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Moc%27ing field_autocomplete.h...</Message>
<Outputs Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">.\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp</Outputs>
<Command Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">"$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fstdafx.h" "-f../../SourceFiles/history/field_autocomplete.h" -DAL_LIBTYPE_STATIC -DUNICODE -DWIN32 -DWIN64 -DHAVE_STDINT_H -DZLIB_WINAPI -D_SCL_SECURE_NO_WARNINGS "-I.\SourceFiles" "-I.\GeneratedFiles" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtCore\5.6.0\QtCore" "-I$(QTDIR)\include\QtGui\5.6.0\QtGui" "-I.\..\..\Libraries\breakpad\src" "-I.\..\..\Libraries\lzma\C" "-I.\..\..\Libraries\libexif-0.6.20" "-I.\..\..\Libraries\zlib-1.2.8" "-I.\..\..\Libraries\ffmpeg" "-I.\..\..\Libraries\openal-soft\include" "-I.\ThirdParty\minizip" "-I.\..\..\Libraries\openssl_debug\Debug\include"</Command>
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs>
<Message Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Moc%27ing field_autocomplete.h...</Message>
<Outputs Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">.\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp</Outputs>
<Command Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">"$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" "-fstdafx.h" "-f../../SourceFiles/history/field_autocomplete.h" -DAL_LIBTYPE_STATIC -DUNICODE -DWIN32 -DWIN64 -DHAVE_STDINT_H -DZLIB_WINAPI -DQT_NO_DEBUG -DNDEBUG -D_SCL_SECURE_NO_WARNINGS "-I.\SourceFiles" "-I.\GeneratedFiles" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtCore\5.6.0\QtCore" "-I$(QTDIR)\include\QtGui\5.6.0\QtGui" "-I.\..\..\Libraries\breakpad\src" "-I.\..\..\Libraries\lzma\C" "-I.\..\..\Libraries\libexif-0.6.20" "-I.\..\..\Libraries\zlib-1.2.8" "-I.\..\..\Libraries\ffmpeg" "-I.\..\..\Libraries\openal-soft\include" "-I.\ThirdParty\minizip" "-I.\..\..\Libraries\openssl\Release\include"</Command>
</CustomBuild>
<ClInclude Include="SourceFiles\history\history_common.h" />
<ClInclude Include="SourceFiles\inline_bots\inline_bot_layout_internal.h" />
<ClInclude Include="SourceFiles\inline_bots\inline_bot_layout_item.h" />
@ -2419,7 +2446,7 @@
</ItemGroup>
<ItemGroup>
<CustomBuild Include="Resources\telegram.qrc">
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">%(FullPath);.\Resources\art\icon256.png;%(AdditionalInputs)</AdditionalInputs>
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">%(FullPath);.\Resources\art\icon256.png;.\Resources\art\sprite_125x.png;.\Resources\art\sprite_150x.png;%(AdditionalInputs)</AdditionalInputs>
<Message Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Rcc%27ing %(Identity)...</Message>
<Outputs Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">.\GeneratedFiles\qrc_%(Filename).cpp;%(Outputs)</Outputs>
<Command Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">"$(QTDIR)\bin\rcc.exe" -name "%(Filename)" -no-compress "%(FullPath)" -o .\GeneratedFiles\qrc_%(Filename).cpp</Command>
@ -2500,7 +2527,18 @@
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Deploy|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
</CodegenStyleItem>
<CodegenStyleItem Include="Resources\basic.style" />
<CustomBuild Include="Resources\basic.style">
<FileType>Document</FileType>
<Command Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">"$(SolutionDir)$(Platform)\codegen\$(Configuration)\codegen_style.exe" "-I.\Resources" "-I.\SourceFiles" "-o.\GeneratedFiles\styles" %(FullPath)</Command>
<Command Condition="'$(Configuration)|$(Platform)'=='Deploy|Win32'">"$(SolutionDir)$(Platform)\codegen\$(Configuration)\codegen_style.exe" "-I.\Resources" "-I.\SourceFiles" "-o.\GeneratedFiles\styles" %(FullPath)</Command>
<Command Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">"$(SolutionDir)$(Platform)\codegen\$(Configuration)\codegen_style.exe" "-I.\Resources" "-I.\SourceFiles" "-o.\GeneratedFiles\styles" %(FullPath)</Command>
<Outputs Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">.\GeneratedFiles\styles\style_%(Filename).h;.\GeneratedFiles\styles\style_%(Filename).cpp;.\Resources\art\sprite_125x.png;.\Resources\art\sprite_150x.png;%(Outputs)</Outputs>
<Outputs Condition="'$(Configuration)|$(Platform)'=='Deploy|Win32'">.\GeneratedFiles\styles\style_%(Filename).h;.\GeneratedFiles\styles\style_%(Filename).cpp;.\Resources\art\sprite_125x.png;.\Resources\art\sprite_150x.png;%(Outputs)</Outputs>
<Outputs Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">.\GeneratedFiles\styles\style_%(Filename).h;.\GeneratedFiles\styles\style_%(Filename).cpp;.\Resources\art\sprite_125x.png;.\Resources\art\sprite_150x.png;%(Outputs)</Outputs>
<Message Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Compiling style %(Identity)...</Message>
<Message Condition="'$(Configuration)|$(Platform)'=='Deploy|Win32'">Compiling style %(Identity)...</Message>
<Message Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Compiling style %(Identity)...</Message>
</CustomBuild>
<CodegenStyleItem Include="Resources\basic_types.style" />
<CodegenStyleItem Include="SourceFiles\overview\overview.style" />
<CodegenStyleItem Include="SourceFiles\profile\profile.style" />

View file

@ -1161,6 +1161,18 @@
<ClCompile Include="GeneratedFiles\Release\moc_profile_fixed_bar.cpp">
<Filter>GeneratedFiles\Release</Filter>
</ClCompile>
<ClCompile Include="GeneratedFiles\Deploy\moc_field_autocomplete.cpp">
<Filter>GeneratedFiles\Deploy</Filter>
</ClCompile>
<ClCompile Include="SourceFiles\history\field_autocomplete.cpp">
<Filter>SourceFiles\history</Filter>
</ClCompile>
<ClCompile Include="GeneratedFiles\Debug\moc_field_autocomplete.cpp">
<Filter>GeneratedFiles\Debug</Filter>
</ClCompile>
<ClCompile Include="GeneratedFiles\Release\moc_field_autocomplete.cpp">
<Filter>GeneratedFiles\Release</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="SourceFiles\stdafx.h">
@ -1576,6 +1588,9 @@
<CustomBuild Include="SourceFiles\core\basic_types.h">
<Filter>SourceFiles\core</Filter>
</CustomBuild>
<CustomBuild Include="SourceFiles\history\field_autocomplete.h">
<Filter>SourceFiles\history</Filter>
</CustomBuild>
<CustomBuild Include="Resources\langs\lang.strings">
<Filter>Resources\langs</Filter>
</CustomBuild>
@ -1591,6 +1606,9 @@
<CustomBuild Include="SourceFiles\profile\profile_fixed_bar.h">
<Filter>SourceFiles\profile</Filter>
</CustomBuild>
<CustomBuild Include="Resources\basic.style">
<Filter>Resources</Filter>
</CustomBuild>
</ItemGroup>
<ItemGroup>
<None Include="Resources\langs\lang_it.strings">
@ -1651,9 +1669,6 @@
<CodegenStyleItem Include="Resources\all_files.style">
<Filter>Resources</Filter>
</CodegenStyleItem>
<CodegenStyleItem Include="Resources\basic.style">
<Filter>Resources</Filter>
</CodegenStyleItem>
<CodegenStyleItem Include="Resources\basic_types.style">
<Filter>Resources</Filter>
</CodegenStyleItem>

View file

@ -75,6 +75,8 @@
076B1C5B1CBFC8F1002C0BC2 /* top_bar_widget.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 076B1C591CBFC8F1002C0BC2 /* top_bar_widget.cpp */; };
076B1C5F1CBFC98F002C0BC2 /* overview_layout.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 076B1C5D1CBFC98F002C0BC2 /* overview_layout.cpp */; };
076B1C631CBFCC53002C0BC2 /* moc_top_bar_widget.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 076B1C621CBFCC53002C0BC2 /* moc_top_bar_widget.cpp */; };
076C51D41CE205120038F22A /* field_autocomplete.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 076C51D21CE205120038F22A /* field_autocomplete.cpp */; };
076C51D71CE2069F0038F22A /* moc_field_autocomplete.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 076C51D61CE2069F0038F22A /* moc_field_autocomplete.cpp */; };
077A4AF71CA41C38002188D2 /* connection_abstract.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 077A4AEC1CA41C38002188D2 /* connection_abstract.cpp */; };
077A4AF81CA41C38002188D2 /* connection_auto.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 077A4AEE1CA41C38002188D2 /* connection_auto.cpp */; };
077A4AF91CA41C38002188D2 /* connection_http.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 077A4AF01CA41C38002188D2 /* connection_http.cpp */; };
@ -396,6 +398,9 @@
076B1C5D1CBFC98F002C0BC2 /* overview_layout.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = overview_layout.cpp; path = SourceFiles/overview/overview_layout.cpp; sourceTree = SOURCE_ROOT; };
076B1C5E1CBFC98F002C0BC2 /* overview_layout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = overview_layout.h; path = SourceFiles/overview/overview_layout.h; sourceTree = SOURCE_ROOT; };
076B1C621CBFCC53002C0BC2 /* moc_top_bar_widget.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = moc_top_bar_widget.cpp; path = GeneratedFiles/Debug/moc_top_bar_widget.cpp; sourceTree = SOURCE_ROOT; };
076C51D21CE205120038F22A /* field_autocomplete.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = field_autocomplete.cpp; path = SourceFiles/history/field_autocomplete.cpp; sourceTree = SOURCE_ROOT; };
076C51D31CE205120038F22A /* field_autocomplete.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = field_autocomplete.h; path = SourceFiles/history/field_autocomplete.h; sourceTree = SOURCE_ROOT; };
076C51D61CE2069F0038F22A /* moc_field_autocomplete.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = moc_field_autocomplete.cpp; path = GeneratedFiles/Debug/moc_field_autocomplete.cpp; sourceTree = SOURCE_ROOT; };
0771C4C94B623FC34BF62983 /* introwidget.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = introwidget.cpp; path = SourceFiles/intro/introwidget.cpp; sourceTree = "<absolute>"; };
077A4AEC1CA41C38002188D2 /* connection_abstract.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = connection_abstract.cpp; path = SourceFiles/mtproto/connection_abstract.cpp; sourceTree = SOURCE_ROOT; };
077A4AED1CA41C38002188D2 /* connection_abstract.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = connection_abstract.h; path = SourceFiles/mtproto/connection_abstract.h; sourceTree = SOURCE_ROOT; };
@ -452,8 +457,6 @@
07BE85111A20961F008ACB9F /* moc_localstorage.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = moc_localstorage.cpp; path = GeneratedFiles/Debug/moc_localstorage.cpp; sourceTree = SOURCE_ROOT; };
07C3AF24194335ED0016CFF1 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = Telegram/Images.xcassets; sourceTree = SOURCE_ROOT; };
07C3AF27194336B90016CFF1 /* pspecific_mac_p.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = pspecific_mac_p.h; path = SourceFiles/pspecific_mac_p.h; sourceTree = SOURCE_ROOT; };
07C3AF2919433ABF0016CFF1 /* style_classes.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = style_classes.txt; path = Resources/style_classes.txt; sourceTree = SOURCE_ROOT; };
07C3AF2A19433ABF0016CFF1 /* style.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = style.txt; path = Resources/style.txt; sourceTree = SOURCE_ROOT; };
07C7596D1B1F7E0000662169 /* autoupdater.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = autoupdater.cpp; path = SourceFiles/autoupdater.cpp; sourceTree = SOURCE_ROOT; };
07C7596E1B1F7E0000662169 /* autoupdater.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = autoupdater.h; path = SourceFiles/autoupdater.h; sourceTree = SOURCE_ROOT; };
07C759711B1F7E2800662169 /* moc_autoupdater.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = moc_autoupdater.cpp; path = GeneratedFiles/Debug/moc_autoupdater.cpp; sourceTree = SOURCE_ROOT; };
@ -844,6 +847,7 @@
074968CB1A44D0B800394F46 /* langs */ = {
isa = PBXGroup;
children = (
07080BCB1A4357F300741A51 /* lang.strings */,
07A190511A723E0A004287AE /* lang_ko.strings */,
072E117A1A56EB9400A87ACC /* lang_pt_BR.strings */,
078DD0241A48DD9E00DD14CC /* lang_de.strings */,
@ -884,6 +888,8 @@
076B1C561CBFC8C9002C0BC2 /* history */ = {
isa = PBXGroup;
children = (
076C51D21CE205120038F22A /* field_autocomplete.cpp */,
076C51D31CE205120038F22A /* field_autocomplete.h */,
076B1C571CBFC8D9002C0BC2 /* history_common.h */,
);
name = history;
@ -911,10 +917,7 @@
isa = PBXGroup;
children = (
074968CB1A44D0B800394F46 /* langs */,
07080BCB1A4357F300741A51 /* lang.strings */,
0747FF811CC644FF00096FC3 /* numbers.txt */,
07C3AF2919433ABF0016CFF1 /* style_classes.txt */,
07C3AF2A19433ABF0016CFF1 /* style.txt */,
07AF95F71AFD03C80060B057 /* telegram_emojis.qrc */,
07AF95F81AFD03C80060B057 /* telegram_mac.qrc */,
1292B92B4848460640F6A391 /* telegram.qrc */,
@ -1207,6 +1210,7 @@
801973D3334D0FCA849CF485 /* Debug */ = {
isa = PBXGroup;
children = (
076C51D61CE2069F0038F22A /* moc_field_autocomplete.cpp */,
076B1C621CBFCC53002C0BC2 /* moc_top_bar_widget.cpp */,
07C8FE111CB80915007A8702 /* moc_toast_manager.cpp */,
077A4AFF1CA41EE2002188D2 /* moc_connection_abstract.cpp */,
@ -1675,6 +1679,7 @@
77DA1217B595B799FB72CDDA /* flatinput.cpp in Compile Sources */,
DE6A34CA3A5561888FA01AF1 /* flatlabel.cpp in Compile Sources */,
07C8FE041CB66D97007A8702 /* inline_bot_send_data.cpp in Compile Sources */,
076C51D41CE205120038F22A /* field_autocomplete.cpp in Compile Sources */,
03270F718426CFE84729079E /* flattextarea.cpp in Compile Sources */,
E3D7A5CA24541D5DB69D6606 /* images.cpp in Compile Sources */,
ADE99904299B99EB6135E8D9 /* scrollarea.cpp in Compile Sources */,
@ -1771,6 +1776,7 @@
07DE92AD1AA4928B00A18F6F /* moc_passcodebox.cpp in Compile Sources */,
FCC949FEA178F9F5D7478027 /* moc_flattextarea.cpp in Compile Sources */,
07DB674D1AD07C9200A51329 /* abstractbox.cpp in Compile Sources */,
076C51D71CE2069F0038F22A /* moc_field_autocomplete.cpp in Compile Sources */,
3F6EB1F5B98E704960FEA686 /* moc_scrollarea.cpp in Compile Sources */,
60CB4898955209B665E7B07D /* moc_twidget.cpp in Compile Sources */,
077A4B051CA41EE2002188D2 /* moc_connection_http.cpp in Compile Sources */,
@ -1903,11 +1909,13 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "";
COPY_PHASE_STRIP = NO;
CRASHPAD_PATH = ./../../Libraries/crashpad/crashpad;
CURRENT_PROJECT_VERSION = "$(TDESKTOP_VERSION)";
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DYLIB_COMPATIBILITY_VERSION = "$(TDESKTOP_MAJOR_VERSION)";
DYLIB_CURRENT_VERSION = "$(TDESKTOP_VERSION)";
ENABLE_STRICT_OBJC_MSGSEND = YES;
FFMPEG_PATH = /usr/local;
FRAMEWORK_SEARCH_PATHS = "";
GCC_GENERATE_DEBUGGING_SYMBOLS = YES;
GCC_LINK_WITH_DYNAMIC_LIBRARIES = NO;
@ -1940,6 +1948,7 @@
"$(CRASHPAD_PATH)",
"$(CRASHPAD_PATH)/third_party/mini_chromium/mini_chromium",
);
ICONV_PATH = /usr/local;
INFOPLIST_FILE = Telegram.plist;
INSTALL_DIR = ./../Mac/Release/;
LDPLUSPLUS = "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang++";
@ -1954,6 +1963,7 @@
);
MACOSX_DEPLOYMENT_TARGET = 10.8;
OBJROOT = "./../Mac/$(CONFIGURATION)Intermediate";
OPENAL_PATH = /usr/local;
OTHER_CFLAGS = (
"-pipe",
"-g",
@ -2015,15 +2025,11 @@
PRODUCT_NAME = Telegram;
QT_LIBRARY_SUFFIX = "";
QT_PATH = "/usr/local/tdesktop/Qt-5.6.0";
ZLIB_PATH = "/usr/local";
FFMPEG_PATH = "/usr/local";
ICONV_PATH = "/usr/local";
CRASHPAD_PATH = "./../../Libraries/crashpad/crashpad";
OPENAL_PATH = "/usr/local";
SDKROOT = macosx;
SYMROOT = ./../Mac;
TDESKTOP_MAJOR_VERSION = 0.9;
TDESKTOP_VERSION = 0.9.48;
TDESKTOP_VERSION = 0.9.49;
ZLIB_PATH = /usr/local;
};
name = Release;
};
@ -2042,12 +2048,14 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "";
COPY_PHASE_STRIP = NO;
CRASHPAD_PATH = ./../../Libraries/crashpad/crashpad;
CURRENT_PROJECT_VERSION = "$(TDESKTOP_VERSION)";
DEBUG_INFORMATION_FORMAT = dwarf;
DYLIB_COMPATIBILITY_VERSION = "$(TDESKTOP_MAJOR_VERSION)";
DYLIB_CURRENT_VERSION = "$(TDESKTOP_VERSION)";
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
FFMPEG_PATH = /usr/local;
FRAMEWORK_SEARCH_PATHS = "";
GCC_GENERATE_DEBUGGING_SYMBOLS = YES;
GCC_LINK_WITH_DYNAMIC_LIBRARIES = NO;
@ -2080,6 +2088,7 @@
"$(CRASHPAD_PATH)",
"$(CRASHPAD_PATH)/third_party/mini_chromium/mini_chromium",
);
ICONV_PATH = /usr/local;
INFOPLIST_FILE = Telegram.plist;
INSTALL_DIR = ./../Mac/Debug/;
LDPLUSPLUS = "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang++";
@ -2095,6 +2104,7 @@
MACOSX_DEPLOYMENT_TARGET = 10.8;
OBJROOT = "./../Mac/$(CONFIGURATION)Intermediate";
ONLY_ACTIVE_ARCH = YES;
OPENAL_PATH = /usr/local;
OTHER_CFLAGS = (
"-pipe",
"-g",
@ -2156,15 +2166,11 @@
PRODUCT_NAME = Telegram;
QT_LIBRARY_SUFFIX = _debug;
QT_PATH = "/usr/local/tdesktop/Qt-5.6.0";
ZLIB_PATH = "/usr/local";
FFMPEG_PATH = "/usr/local";
ICONV_PATH = "/usr/local";
CRASHPAD_PATH = "./../../Libraries/crashpad/crashpad";
OPENAL_PATH = "/usr/local";
SDKROOT = macosx;
SYMROOT = ./../Mac;
TDESKTOP_MAJOR_VERSION = 0.9;
TDESKTOP_VERSION = 0.9.48;
TDESKTOP_VERSION = 0.9.49;
ZLIB_PATH = /usr/local;
};
name = Debug;
};

View file

@ -111,6 +111,7 @@ compilers: GeneratedFiles/qrc_telegram.cpp\
GeneratedFiles/Debug/moc_sessionsbox.cpp\
GeneratedFiles/Debug/moc_stickersetbox.cpp\
GeneratedFiles/Debug/moc_usernamebox.cpp\
GeneratedFiles/Debug/moc_field_autocomplete.cpp\
GeneratedFiles/Debug/moc_introwidget.cpp\
GeneratedFiles/Debug/moc_introcode.cpp\
GeneratedFiles/Debug/moc_introphone.cpp\
@ -237,6 +238,7 @@ compiler_moc_header_make_all: GeneratedFiles/Debug/moc_apiwrap.cpp\
GeneratedFiles/Debug/moc_sessionsbox.cpp\
GeneratedFiles/Debug/moc_stickersetbox.cpp\
GeneratedFiles/Debug/moc_usernamebox.cpp\
GeneratedFiles/Debug/moc_field_autocomplete.cpp\
GeneratedFiles/Debug/moc_introwidget.cpp\
GeneratedFiles/Debug/moc_introcode.cpp\
GeneratedFiles/Debug/moc_introphone.cpp\
@ -306,6 +308,7 @@ compiler_moc_header_clean:
GeneratedFiles/Debug/moc_sessionsbox.cpp\
GeneratedFiles/Debug/moc_stickersetbox.cpp\
GeneratedFiles/Debug/moc_usernamebox.cpp\
GeneratedFiles/Debug/moc_field_autocomplete.cpp\
GeneratedFiles/Debug/moc_introwidget.cpp\
GeneratedFiles/Debug/moc_introcode.cpp\
GeneratedFiles/Debug/moc_introphone.cpp\
@ -498,6 +501,9 @@ GeneratedFiles/Debug/moc_stickersetbox.cpp: SourceFiles/boxes/stickersetbox.h
GeneratedFiles/Debug/moc_usernamebox.cpp:SourceFiles/boxes/usernamebox.h
$(MOC_FILE) SourceFiles/boxes/usernamebox.h -o GeneratedFiles/Debug/moc_usernamebox.cpp
GeneratedFiles/Debug/moc_field_autocomplete.cpp: SourceFiles/history/field_autocomplete.h
$(MOC_FILE) SourceFiles/history/field_autocomplete.h -o GeneratedFiles/Debug/moc_field_autocomplete.cpp
GeneratedFiles/Debug/moc_introwidget.cpp: SourceFiles/intro/introwidget.h
$(MOC_FILE) SourceFiles/intro/introwidget.h -o GeneratedFiles/Debug/moc_introwidget.cpp

View file

@ -1,6 +1,6 @@
AppVersion 9048
AppVersion 9049
AppVersionStrMajor 0.9
AppVersionStrSmall 0.9.48
AppVersionStr 0.9.48
AppVersionStrSmall 0.9.49
AppVersionStr 0.9.49
AlphaChannel 0
BetaVersion 0