Merge branch 'dev'
|
@ -141,6 +141,8 @@ build() {
|
|||
|
||||
# Patch tdesktop
|
||||
sed -i 's/CUSTOM_API_ID//g' "$UPSTREAM/Telegram/Telegram.pro"
|
||||
sed -i 's,LIBS += /usr/local/lib/libxkbcommon.a,,g" "$UPSTREAM/Telegram/Telegram.pro"
|
||||
sed -i 's,#xkbcommon,xkbcommon,g" "$UPSTREAM/Telegram/Telegram.pro"
|
||||
sed -i "s,\..*/Libraries/breakpad/,$BREAKPAD_PATH/,g" "$UPSTREAM/Telegram/Telegram.pro"
|
||||
|
||||
local options=""
|
||||
|
@ -321,4 +323,4 @@ check() {
|
|||
|
||||
source ./.travis/common.sh
|
||||
|
||||
run
|
||||
run
|
||||
|
|
|
@ -11300,6 +11300,34 @@ index ca92103..225d85f 100644
|
|||
QPainter p(&m_qImage);
|
||||
p.setCompositionMode(QPainter::CompositionMode_Source);
|
||||
const QVector<QRect> rects = region.rects();
|
||||
diff --git a/src/plugins/platforms/cocoa/qcocoaintegration.mm b/src/plugins/platforms/cocoa/qcocoaintegration.mm
|
||||
index 6bec6b1..f14d6ee 100644
|
||||
--- a/src/plugins/platforms/cocoa/qcocoaintegration.mm
|
||||
+++ b/src/plugins/platforms/cocoa/qcocoaintegration.mm
|
||||
@@ -422,14 +422,20 @@ void QCocoaIntegration::updateScreens()
|
||||
}
|
||||
siblings << screen;
|
||||
}
|
||||
+
|
||||
+
|
||||
+// Patch: backport crash fix from Qt 5.6.1
|
||||
+ // Set virtual siblings list. All screens in mScreens are siblings, because we ignored the
|
||||
+ // mirrors. Note that some of the screens we update the siblings list for here may be deleted
|
||||
+ // below, but update anyway to keep the to-be-deleted screens out of the siblings list.
|
||||
+ foreach (QCocoaScreen* screen, mScreens)
|
||||
+ screen->setVirtualSiblings(siblings);
|
||||
+
|
||||
// Now the leftovers in remainingScreens are no longer current, so we can delete them.
|
||||
foreach (QCocoaScreen* screen, remainingScreens) {
|
||||
mScreens.removeOne(screen);
|
||||
destroyScreen(screen);
|
||||
}
|
||||
- // All screens in mScreens are siblings, because we ignored the mirrors.
|
||||
- foreach (QCocoaScreen* screen, mScreens)
|
||||
- screen->setVirtualSiblings(siblings);
|
||||
}
|
||||
|
||||
QCocoaScreen *QCocoaIntegration::screenAtIndex(int index)
|
||||
diff --git a/src/plugins/platforms/cocoa/qcocoakeymapper.mm b/src/plugins/platforms/cocoa/qcocoakeymapper.mm
|
||||
index c2d206f..0f9b512 100644
|
||||
--- a/src/plugins/platforms/cocoa/qcocoakeymapper.mm
|
||||
|
|
|
@ -23,7 +23,10 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
using "basic_types.style";
|
||||
using "basic.style";
|
||||
|
||||
using "boxes/boxes.style";
|
||||
using "dialogs/dialogs.style";
|
||||
using "history/history.style";
|
||||
using "overview/overview.style";
|
||||
using "profile/profile.style";
|
||||
using "media/view/mediaview.style";
|
||||
using "ui/widgets/widgets.style";
|
||||
|
|
Before Width: | Height: | Size: 176 KiB After Width: | Height: | Size: 176 KiB |
Before Width: | Height: | Size: 239 KiB After Width: | Height: | Size: 238 KiB |
|
@ -1868,6 +1868,9 @@ stickersSettings: sprite(140px, 124px, 21px, 22px);
|
|||
savedGifsOver: sprite(329px, 286px, 21px, 22px);
|
||||
savedGifsActive: sprite(350px, 286px, 21px, 22px);
|
||||
|
||||
stickersSettingsUnreadSize: 17px;
|
||||
stickersSettingsUnreadPosition: point(4px, 5px);
|
||||
|
||||
emojiPanCategories: #f7f7f7;
|
||||
|
||||
rbEmoji: flatCheckbox {
|
||||
|
@ -2141,7 +2144,9 @@ mvCaptionRadius: 2px;
|
|||
mvCaptionBg: #11111180;
|
||||
mvCaptionFont: font(fsize);
|
||||
|
||||
medviewSaveMsgCheck: sprite(311px, 309px, 22px, 18px);
|
||||
medviewSaveMsgCheck: icon {
|
||||
{ "mediaview_save_check", #ffffff }
|
||||
};
|
||||
medviewSaveMsgFont: font(16px);
|
||||
medviewSaveMsgPadding: margins(55px, 19px, 29px, 20px);
|
||||
medviewSaveMsgCheckPos: point(23px, 21px);
|
||||
|
|
|
@ -450,3 +450,17 @@ OutlineButton {
|
|||
font: font;
|
||||
padding: margins;
|
||||
}
|
||||
|
||||
IconButton {
|
||||
width: pixels;
|
||||
height: pixels;
|
||||
|
||||
opacity: double;
|
||||
overOpacity: double;
|
||||
|
||||
icon: icon;
|
||||
iconPosition: point;
|
||||
downIconPosition: point;
|
||||
|
||||
duration: int;
|
||||
}
|
||||
|
|
BIN
Telegram/Resources/icons/media_fullscreen_from.png
Normal file
After Width: | Height: | Size: 223 B |
BIN
Telegram/Resources/icons/media_fullscreen_from@2x.png
Normal file
After Width: | Height: | Size: 399 B |
BIN
Telegram/Resources/icons/media_fullscreen_to.png
Normal file
After Width: | Height: | Size: 225 B |
BIN
Telegram/Resources/icons/media_fullscreen_to@2x.png
Normal file
After Width: | Height: | Size: 396 B |
BIN
Telegram/Resources/icons/media_pause.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
Telegram/Resources/icons/media_pause@2x.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
Telegram/Resources/icons/media_play.png
Normal file
After Width: | Height: | Size: 337 B |
BIN
Telegram/Resources/icons/media_play@2x.png
Normal file
After Width: | Height: | Size: 381 B |
BIN
Telegram/Resources/icons/media_volume.png
Normal file
After Width: | Height: | Size: 148 B |
BIN
Telegram/Resources/icons/media_volume@2x.png
Normal file
After Width: | Height: | Size: 209 B |
BIN
Telegram/Resources/icons/mediaview_save_check.png
Normal file
After Width: | Height: | Size: 454 B |
BIN
Telegram/Resources/icons/mediaview_save_check@2x.png
Normal file
After Width: | Height: | Size: 882 B |
BIN
Telegram/Resources/icons/stickers_add.png
Normal file
After Width: | Height: | Size: 173 B |
BIN
Telegram/Resources/icons/stickers_add@2x.png
Normal file
After Width: | Height: | Size: 120 B |
|
@ -190,6 +190,11 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_signin_reset_account" = "Reset your account";
|
||||
"lng_signin_sure_reset" = "Warning!\n\nYou will lose all your chats and messages, along with any media and files you shared!\n\nDo you want to reset your account?";
|
||||
"lng_signin_reset" = "Reset";
|
||||
"lng_signin_reset_wait" = "Since the account {phone_number} is active and protected by a password, we will delete it in 1 week for security purposes. You can cancel this process at any time.\n\nYou’ll be able to reset your account in:\n{when}";
|
||||
"lng_signin_reset_in_days" = "{count_days:0 days|# day|# days} {count_hours:0 hours|# hour|# hours} {count_minutes:0 minutes|# minute|# minutes}";
|
||||
"lng_signin_reset_in_hours" = "{count_hours:0 hours|# hour|# hours} {count_minutes:0 minutes|# minute|# minutes}";
|
||||
"lng_signin_reset_in_minutes" = "{count_minutes:0 minutes|# minute|# minutes}";
|
||||
"lng_signin_reset_cancelled" = "Your recent attempts to reset this account have been cancelled by its active user. Please try again in 7 days.";
|
||||
|
||||
"lng_signup_title" = "Information and photo";
|
||||
"lng_signup_desc" = "Please enter your name and\nupload a photo.";
|
||||
|
@ -602,6 +607,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_group_invite_want_join_channel" = "Do you want to join the channel «{title}»?";
|
||||
"lng_group_invite_join" = "Join";
|
||||
|
||||
"lng_group_invite_members" = "{count:_not_used_|# member|# members}, among them:";
|
||||
|
||||
"lng_group_invite_link" = "Invite link:";
|
||||
"lng_group_invite_create" = "Create an invite link";
|
||||
"lng_group_invite_about" = "Telegram users will be able to join\nyour group by following this link.";
|
||||
|
@ -664,6 +671,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_emoji_category6" = "Objects";
|
||||
"lng_emoji_category7" = "Symbols & Flags";
|
||||
|
||||
"lng_recent_stickers" = "Frequently used";
|
||||
"lng_switch_stickers" = "Stickers";
|
||||
"lng_switch_stickers_gifs" = "GIFs & Stickers";
|
||||
"lng_switch_emoji" = "Emoji";
|
||||
|
@ -680,12 +688,16 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_stickers_add_pack" = "Add stickers";
|
||||
"lng_stickers_share_pack" = "Share Stickers";
|
||||
"lng_stickers_not_found" = "Sticker pack not found.";
|
||||
"lng_stickers_too_many_packs" = "You have too many sticker packs. Please remove some first.";
|
||||
"lng_stickers_packs_archived" = "Some of your unused stickers have been archived to make room for the sets you've activated.";
|
||||
"lng_stickers_archived" = "Archived Stickers";
|
||||
"lng_stickers_copied" = "Sticker pack link copied to clipboard.";
|
||||
"lng_stickers_default_set" = "Great Minds";
|
||||
"lng_stickers_you_have" = "Manage and reorder sticker packs";
|
||||
"lng_stickers_packs" = "Sticker Packs";
|
||||
"lng_stickers_reorder" = "Click and drag to reorder sticker packs";
|
||||
"lng_stickers_featured" = "Trending Stickers";
|
||||
"lng_stickers_clear_recent" = "Clear";
|
||||
"lng_stickers_clear_recent_sure" = "Are you sure you want to clear your frequently used stickers list?";
|
||||
"lng_stickers_remove" = "Delete";
|
||||
"lng_stickers_return" = "Undo";
|
||||
"lng_stickers_restore" = "Restore";
|
||||
|
@ -922,12 +934,19 @@ 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" = "— Fixed photo viewer to handle screen resolution change correctly\n— Fixed forwarding photos via drag-n-drop\n— Various design improvements and other bug fixes";
|
||||
"lng_new_version_text" = "— Trending stickers. Check out and install noteworthy sticker packs from the new tab in Settings.\n— Archived stickers. Unused stickers are now archived automatically when you go over the 200 limit.\n— Group previews. Preview groups before joining them via invite link – see who else is in the group before joining.\n— New internal video player.\n— Improved design for chats.";
|
||||
|
||||
"lng_menu_insert_unicode" = "Insert Unicode control character";
|
||||
|
||||
"lng_full_name" = "{first_name} {last_name}";
|
||||
|
||||
"lng_confirm_phone_link_invalid" = "This link is broken or has expired.";
|
||||
"lng_confirm_phone_title" = "Cancel account reset";
|
||||
"lng_confirm_phone_about" = "Somebody with access to your phone number {phone} has requested to delete your Telegram account and reset your 2-Step Verification password.\n\nIf this wasn't you, please enter the code we've just sent you via SMS to your number.";
|
||||
"lng_confirm_phone_success" = "Success!\n\nThe deletion process was cancelled for your account {phone}. You may close this window now.";
|
||||
"lng_confirm_phone_send" = "Send";
|
||||
"lng_confirm_phone_enter_code" = "Please enter the code.";
|
||||
|
||||
// Not used
|
||||
|
||||
"lng_topbar_info" = "Info";
|
||||
|
|
|
@ -190,6 +190,11 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_signin_reset_account" = "Konto zurücksetzen";
|
||||
"lng_signin_sure_reset" = "Hinweis!\n\nDu verlierst du alle Chats und Nachrichten, ebenso deine geteilten Bilder und Videos.\n\nKonto wirklich zurücksetzen?";
|
||||
"lng_signin_reset" = "Zurücksetzen";
|
||||
"lng_signin_reset_wait" = "Da dein Konto {phone_number} aktiv und durch ein Kennwort geschützt ist, löschen wir es aus Sicherheitsgründen in einer Woche. Du kannst den Vorgang jederzeit abbrechen.\n\nDu kannst dein Konto zurücksetzen in:\n{when}";
|
||||
"lng_signin_reset_in_days" = "{count_days:0 Tage|# Tag|# Tage} {count_hours:0 Stunden|# Stunde|# Stunden} {count_minutes:0 Minuten|# Minute|# Minuten}";
|
||||
"lng_signin_reset_in_hours" = "{count_hours:0 Stunden|# Stunde|# Stunden} {count_minutes:0 Minuten|# Minute|# Minuten}";
|
||||
"lng_signin_reset_in_minutes" = "{count_minutes:0 Minuten|# Minute|# Minuten}";
|
||||
"lng_signin_reset_cancelled" = "Deine vorherigen Versuche das Konto zurückzusetzen wurden durch den aktiven Nutzer abgebrochen. Bitte in 7 Tagen erneut probieren.";
|
||||
|
||||
"lng_signup_title" = "Information und Bild";
|
||||
"lng_signup_desc" = "Bitte trage deinen Namen ein \nund lade ein Bild hoch.";
|
||||
|
@ -602,6 +607,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_group_invite_want_join_channel" = "Möchtest du dem Kanal «{title}» beitreten?";
|
||||
"lng_group_invite_join" = "Beitreten";
|
||||
|
||||
"lng_group_invite_members" = "{count:_not_used_|# Mitglied|# Mitglieder}, darunter:";
|
||||
|
||||
"lng_group_invite_link" = "Einladungslink:";
|
||||
"lng_group_invite_create" = "Neuer Link";
|
||||
"lng_group_invite_about" = "Jeder, der Telegram installiert hat,\nkann anhand dieses Links in deine Gruppe.";
|
||||
|
@ -664,6 +671,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_emoji_category6" = "Objekte";
|
||||
"lng_emoji_category7" = "Symbole & Flaggen";
|
||||
|
||||
"lng_recent_stickers" = "Häufig genutzt";
|
||||
"lng_switch_stickers" = "Sticker";
|
||||
"lng_switch_stickers_gifs" = "GIFs & Sticker";
|
||||
"lng_switch_emoji" = "Emoji";
|
||||
|
@ -680,11 +688,16 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_stickers_add_pack" = "Sticker hinzufügen";
|
||||
"lng_stickers_share_pack" = "Sticker teilen";
|
||||
"lng_stickers_not_found" = "Sticker-Paket nicht gefunden.";
|
||||
"lng_stickers_packs_archived" = "Einige deiner unbenutzen Sticker wurden archiviert, damit du Platz für neue Sticker hast.";
|
||||
"lng_stickers_archived" = "Archivierte Sticker";
|
||||
"lng_stickers_copied" = "Sticker-Paket Link in die Zwischenablage kopiert.";
|
||||
"lng_stickers_default_set" = "Große Denker";
|
||||
"lng_stickers_you_have" = "Sticker-Pakete verwalten";
|
||||
"lng_stickers_packs" = "Sticker-Pakete";
|
||||
"lng_stickers_reorder" = "Paket gedrückt halten und verschieben um die Anordnung zu ändern";
|
||||
"lng_stickers_featured" = "Angesagte Sticker";
|
||||
"lng_stickers_clear_recent" = "Leeren";
|
||||
"lng_stickers_clear_recent_sure" = "Zuletzt benutzte Sticker leeren?";
|
||||
"lng_stickers_remove" = "Löschen";
|
||||
"lng_stickers_return" = "Rückgängig";
|
||||
"lng_stickers_restore" = "Zeigen";
|
||||
|
@ -881,7 +894,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_selected_forward" = "Weiterleiten";
|
||||
"lng_selected_count" = "{count:_not_used_|# Nachricht|# Nachrichten}";
|
||||
"lng_selected_cancel_sure_this" = "Upload abbrechen?";
|
||||
"lng_selected_upload_stop" = "Abbrechen";
|
||||
"lng_selected_upload_stop" = "Stoppen";
|
||||
"lng_selected_delete_sure_this" = "Diese Nachricht wirklich löschen?";
|
||||
"lng_selected_delete_sure" = "Willst du {count:_not_used_|# Nachricht|# Nachrichten} löschen?";
|
||||
"lng_delete_photo_sure" = "Dieses Bild wirklich löschen?";
|
||||
|
@ -921,12 +934,19 @@ 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" = "— Fehlerbehebung: Bildbetrachter sollte Änderungen der Bildschirmauflösung korrekt handhaben\n— Fehlerbehebung: Bilder per Drag’n’Drop weiterleiten\n— Verschiedene optische Verbesserungen und sonstige Fehlerbehebungen";
|
||||
"lng_new_version_text" = "— Angesagte Sticker: In den Einstellungen, im Bereich Sticker, stehen interessante Stickerpakete bereit.\n— Archivierte Sticker: Unbenutze Sticker werden automatisch archiviert, sobald das Limit (200) erreicht wird.\n— Gruppenvorschau: Vor dem Betreten der Gruppe wird dir angezeigt, wer bereits Mitglied ist.\n— Neuer interner Videoplayer.\n— Verbessertes Chatdesign.";
|
||||
|
||||
"lng_menu_insert_unicode" = "Unicode-Steuerzeichen einfügen";
|
||||
|
||||
"lng_full_name" = "{first_name} {last_name}";
|
||||
|
||||
"lng_confirm_phone_link_invalid" = "Dieser Link ist abgelaufen oder ungültig.";
|
||||
"lng_confirm_phone_title" = "Zurücksetzung abbrechen";
|
||||
"lng_confirm_phone_about" = "Jemand mit Zugang zu deiner Telefonnummer {phone} hat die Kontolöschung und Zurücksetzung der zweistufige Bestätigung beantragt.\n\nWenn du das nicht selbst gewesen bist, tippe den Code der SMS ein, den wir dir gerade gesendet haben.";
|
||||
"lng_confirm_phone_success" = "Geschafft!\n\nDer Löschvorgang für dein Konto {phone} wurde abgebrochen. Du kannst dieses Fenster jetzt schließen.";
|
||||
"lng_confirm_phone_send" = "Senden";
|
||||
"lng_confirm_phone_enter_code" = "Bitte den Code eingeben.";
|
||||
|
||||
// Not used
|
||||
|
||||
"lng_topbar_info" = "Info";
|
||||
|
|
|
@ -190,6 +190,11 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_signin_reset_account" = "Restablecer tu cuenta";
|
||||
"lng_signin_sure_reset" = "¡Advertencia!\n\n¡Perderás todos tus chats y mensajes, junto con la multimedia y archivos compartidos!\n\n¿Quieres restablecer tu cuenta?";
|
||||
"lng_signin_reset" = "Restablecer";
|
||||
"lng_signin_reset_wait" = "Como la cuenta {phone_number} está activa y protegida con una contraseña, la eliminaremos en 1 semana, por motivos de seguridad. Puedes cancelar el proceso en cualquier momento.\n\nPodrás restablecer tu cuenta en:\n{when}";
|
||||
"lng_signin_reset_in_days" = "{count_days:0 días|# día|# días} {count_hours:0 horas|# hora|# horas} {count_minutes:0 minutos|# minuto|# minutos}";
|
||||
"lng_signin_reset_in_hours" = "{count_hours:0 horas|# hora|# horas} {count_minutes:0 minutos|# minuto|# minutos}";
|
||||
"lng_signin_reset_in_minutes" = "{count_minutes:0 minutos|# minuto|# minutos}";
|
||||
"lng_signin_reset_cancelled" = "Tus intentos recientes para restablecer la cuenta fueron cancelados por su usuario activo. Por favor, reinténtalo en 7 días.";
|
||||
|
||||
"lng_signup_title" = "Información y foto";
|
||||
"lng_signup_desc" = "Por favor, pon tu nombre \ny una foto.";
|
||||
|
@ -602,6 +607,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_group_invite_want_join_channel" = "¿Quieres unirte al canal «{title}»?";
|
||||
"lng_group_invite_join" = "Unirme";
|
||||
|
||||
"lng_group_invite_members" = "{count:_not_used_|# miembro|# miembros}, entre ellos:";
|
||||
|
||||
"lng_group_invite_link" = "Enlace de invitación:";
|
||||
"lng_group_invite_create" = "Crear un enlace de invitación";
|
||||
"lng_group_invite_about" = "Los usuarios de Telegram podrán unirse\na tu grupo a través de este enlace.";
|
||||
|
@ -664,6 +671,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_emoji_category6" = "Objetos";
|
||||
"lng_emoji_category7" = "Símbolos y banderas";
|
||||
|
||||
"lng_recent_stickers" = "Uso frecuente";
|
||||
"lng_switch_stickers" = "Stickers";
|
||||
"lng_switch_stickers_gifs" = "GIF y stickers";
|
||||
"lng_switch_emoji" = "Emoji";
|
||||
|
@ -680,11 +688,16 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_stickers_add_pack" = "Añadir stickers";
|
||||
"lng_stickers_share_pack" = "Compartir stickers";
|
||||
"lng_stickers_not_found" = "Pack de stickers no encontrado.";
|
||||
"lng_stickers_packs_archived" = "Algunos de los stickers que no usas fueron archivados. Así tendrás más espacio para los packs que activaste.";
|
||||
"lng_stickers_archived" = "Stickers archivados";
|
||||
"lng_stickers_copied" = "Enlace del pack de stickers copiado al portapapeles.";
|
||||
"lng_stickers_default_set" = "Grandes personajes";
|
||||
"lng_stickers_you_have" = "Administrar y ordenar los packs de stickers";
|
||||
"lng_stickers_packs" = "Packs de stickers";
|
||||
"lng_stickers_reorder" = "Haz clic y arrastra para ordenar los packs";
|
||||
"lng_stickers_featured" = "Stickers destacados";
|
||||
"lng_stickers_clear_recent" = "Borrar";
|
||||
"lng_stickers_clear_recent_sure" = "¿Quieres borrar la lista de stickers usados frecuentemente?";
|
||||
"lng_stickers_remove" = "Eliminar";
|
||||
"lng_stickers_return" = "Deshacer";
|
||||
"lng_stickers_restore" = "Restaurar";
|
||||
|
@ -880,7 +893,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_selected_delete" = "Eliminar";
|
||||
"lng_selected_forward" = "Reenviar";
|
||||
"lng_selected_count" = "{count:_not_used_|# mensaje|# mensajes}";
|
||||
"lng_selected_cancel_sure_this" = "¿Cancelar envío?";
|
||||
"lng_selected_cancel_sure_this" = "¿Detener el envío?";
|
||||
"lng_selected_upload_stop" = "Detener";
|
||||
"lng_selected_delete_sure_this" = "¿Quieres eliminar este mensaje?";
|
||||
"lng_selected_delete_sure" = "¿Quieres eliminar {count:_not_used_|# mensaje|# mensajes}?";
|
||||
|
@ -921,12 +934,19 @@ 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" = "— Corrección en el visor de fotos, para manejar correctamente el cambio de la resolución de la pantalla\n— Corrección en el reenvío de fotos a través de arrastrar y soltar \n— Varias mejoras de diseño y corrección de otros errores";
|
||||
"lng_new_version_text" = "— Stickers destacados. Mira e instala packs de stickers desde la nueva pestaña en Ajustes.\n— Stickers archivados. Ahora, los packs de stickers que no usas, se archivan automáticamente cuando superas el límite de 200.\n— Vista previa de grupos. Mira un grupo antes de unirte a él a través del enlace de invitación. Mira quién está en el grupo antes de unirte.\n— Nuevo reproductor interno de vídeo.\n— Diseño mejorado de chats.";
|
||||
|
||||
"lng_menu_insert_unicode" = "Insertar caracteres de control Unicode";
|
||||
|
||||
"lng_full_name" = "{first_name} {last_name}";
|
||||
|
||||
"lng_confirm_phone_link_invalid" = "El enlace está roto o ha expirado.";
|
||||
"lng_confirm_phone_title" = "No restablecer la cuenta";
|
||||
"lng_confirm_phone_about" = "Alguien, con acceso a tu número de teléfono {phone}, solicitó eliminar tu cuenta de Telegram y restablecer tu contraseña de la verificación en dos pasos.\n\nSi no eras tú, por favor, inserta el código que enviamos por SMS a tu número.";
|
||||
"lng_confirm_phone_success" = "¡Listo!\n\nEl proceso de eliminación de la cuenta {phone} fue cancelado. Puedes cerrar esta ventana ahora.";
|
||||
"lng_confirm_phone_send" = "Enviar";
|
||||
"lng_confirm_phone_enter_code" = "Por favor, pon el código.";
|
||||
|
||||
// Not used
|
||||
|
||||
"lng_topbar_info" = "Información";
|
||||
|
|
|
@ -190,6 +190,11 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_signin_reset_account" = "Ripristina il tuo account";
|
||||
"lng_signin_sure_reset" = "Attenzione!\n\nPerderai tutte le chat e i messaggi, insieme a tutti i media e i file condivisi!\n\nVuoi ripristinare il tuo account?";
|
||||
"lng_signin_reset" = "Ripristina";
|
||||
"lng_signin_reset_wait" = "Dato che l'account {phone_number} è attivo e protetto da una password, lo elimineremo tra 1 settimana per motivi di sicurezza. Puoi annullare questo processo in qualsiasi momento.\n\nSarai in grado di ripristinare il tuo account tra:\n{when}";
|
||||
"lng_signin_reset_in_days" = "{count_days:0 giorni|# giorno|# giorni} {count_hours:0 ore|# ora|# ore} {count_minutes:0 minuti|# minuto|# minuti}";
|
||||
"lng_signin_reset_in_hours" = "{count_hours:0 ore|# ora|# ore} {count_minutes:0 minuti|# minuto|# minuti}";
|
||||
"lng_signin_reset_in_minutes" = "{count_minutes:0 minuti|# minuto|# minuti}";
|
||||
"lng_signin_reset_cancelled" = "I tuoi tentativi recenti di ripristinare questo account sono stati annullati dal suo utente attivo. Per favore riprova tra 7 giorni.";
|
||||
|
||||
"lng_signup_title" = "Info e foto";
|
||||
"lng_signup_desc" = "Inserisci il tuo nome e\ncarica una foto.";
|
||||
|
@ -202,7 +207,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_dlg_new_channel_name" = "Nome canale";
|
||||
"lng_no_contacts" = "Non hai contatti";
|
||||
"lng_no_chats" = "Le tua chat saranno qui";
|
||||
"lng_contacts_loading" = "Caricamento...";
|
||||
"lng_contacts_loading" = "Carico...";
|
||||
"lng_contacts_not_found" = "Nessun contatto trovato";
|
||||
"lng_dlg_search_chat" = "Cerca in questa chat";
|
||||
"lng_dlg_search_channel" = "Cerca in questo canale";
|
||||
|
@ -211,7 +216,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_settings_save" = "Salva";
|
||||
"lng_settings_upload" = "Imposta foto profilo";
|
||||
"lng_settings_crop_profile" = "Seleziona un riquadro per la tua foto profilo";
|
||||
"lng_settings_uploading_photo" = "Caricamento foto...";
|
||||
"lng_settings_uploading_photo" = "Carico foto...";
|
||||
|
||||
"lng_username_title" = "Username";
|
||||
"lng_username_about" = "Puoi scegliere un username su Telegram.\nSe lo fai, le altre persone potranno trovarti\ntramite questo username e contattarti \nsenza conoscere il tuo numero di telefono.\n\nPuoi usare a-z, 0-9 e underscore.\nLa lunghezza minima è di 5 caratteri.";
|
||||
|
@ -348,7 +353,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_cloud_password_email" = "Inserisci email di recupero";
|
||||
"lng_cloud_password_bad_email" = "E-mail non valida, riprova con un'altra.";
|
||||
"lng_cloud_password_about" = "La password sarà richiesta quando ti connetti da un nuovo dispositivo insieme al codice.";
|
||||
"lng_cloud_password_about_recover" = "Attenzione! Sei sicuro di non voler\naggiungere un'e-mail di recupero?\n\nSe dimentichi la tua password, perderai\nl'accesso al tuo account Telegram.";
|
||||
"lng_cloud_password_about_recover" = "Attenzione! Sicuro di non voler\naggiungere un'e-mail di recupero?\n\nSe dimentichi la tua password, perderai\nl'accesso al tuo account Telegram.";
|
||||
"lng_cloud_password_skip_email" = "Salta e-mail";
|
||||
"lng_cloud_password_almost" = "Abbiamo inviato un link di conferma\nall'e-mail che ci hai fornito. La verifica in due passaggi sarà attivata non appena aprirai quel link.";
|
||||
"lng_cloud_password_was_set" = "Verifica in due passaggi abilitata.";
|
||||
|
@ -445,7 +450,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_profile_sure_kick" = "Rimuovere {user} dal gruppo?";
|
||||
"lng_profile_sure_kick_channel" = "Rimuovere {user} dal canale?";
|
||||
"lng_profile_sure_kick_admin" = "Rimuovere {user} dagli amministratori?";
|
||||
"lng_profile_loading" = "Caricamento...";
|
||||
"lng_profile_loading" = "Carico...";
|
||||
"lng_profile_shared_media" = "Media condivisi";
|
||||
"lng_profile_no_media" = "Nessun media in questa chat.";
|
||||
"lng_profile_photos" = "{count:_not_used_|# foto|# foto}";
|
||||
|
@ -528,10 +533,10 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_sure_delete_history" = "Sicuro di voler eliminare tutta la cronologia dei messaggi con {contact}?\n\nQuesta azione non può essere annullata.";
|
||||
"lng_sure_delete_group_history" = "Sicuro di voler eliminare tutta la cronologia dei messaggi in «{group}»?\n\nQuesta azione non può essere annullata.";
|
||||
"lng_sure_delete_and_exit" = "Sicuro di voler eliminare tutta la cronologia dei messaggi e abbandonare «{group}»?\n\nQuesta azione non può essere annullata.";
|
||||
"lng_sure_leave_channel" = "Sei sicuro di voler lasciare\nquesto canale?";
|
||||
"lng_sure_delete_channel" = "Sei sicuro di voler eliminare questo canale? Tutti i membri verranno rimossi e i messaggi verranno persi.";
|
||||
"lng_sure_leave_channel" = "Sicuro di voler lasciare\nquesto canale?";
|
||||
"lng_sure_delete_channel" = "Sicuro di voler eliminare questo canale? Tutti i membri verranno rimossi e i messaggi verranno persi.";
|
||||
"lng_sure_leave_group" = "Sicuro di voler lasciare questo gruppo?\nQuesta azione non può essere annullata.";
|
||||
"lng_sure_delete_group" = "Sei sicuro di voler eliminare questo gruppo? Tutti i membri verranno rimossi e i messaggi verranno persi.";
|
||||
"lng_sure_delete_group" = "Sicuro di voler eliminare questo gruppo? Tutti i membri verranno rimossi e i messaggi verranno persi.";
|
||||
|
||||
"lng_message_empty" = "Messaggio vuoto";
|
||||
"lng_message_unsupported" = "Questo messaggio non è supportato dalla tua versione di Telegram Desktop. Per favore, aggiorna all'ultima versione dalle Impostazioni o installalo da {link}";
|
||||
|
@ -585,7 +590,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_profile_convert_about" = "Nei supergruppi:";
|
||||
"lng_profile_convert_feature1" = "— I nuovi membri vedono tutta la cronologia";
|
||||
"lng_profile_convert_feature2" = "— I messaggi eliminati scompaiono per tutti";
|
||||
"lng_profile_convert_feature3" = "— Gli amministratori possono fissare i messaggi";
|
||||
"lng_profile_convert_feature3" = "— Gli admin possono fissare i messaggi importanti";
|
||||
"lng_profile_convert_feature4" = "— Il creatore può creare un link pubblico per il gruppo";
|
||||
"lng_profile_convert_warning" = "{bold_start}Nota:{bold_end} Questa azione non può essere annullata";
|
||||
"lng_profile_convert_confirm" = "Converti";
|
||||
|
@ -602,6 +607,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_group_invite_want_join_channel" = "Vuoi unirti al canale «{title}»?";
|
||||
"lng_group_invite_join" = "Unisciti";
|
||||
|
||||
"lng_group_invite_members" = "{count:_not_used_|# membro|# membri}, tra cui:";
|
||||
|
||||
"lng_group_invite_link" = "Link di invito:";
|
||||
"lng_group_invite_create" = "Crea un link di invito";
|
||||
"lng_group_invite_about" = "Gli utenti di Telegram potranno \nunirsi al tuo gruppo aprendo il link.";
|
||||
|
@ -655,7 +662,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_media_auto_groups" = "Gruppi e canali";
|
||||
"lng_media_auto_play" = "Autoriproduzione";
|
||||
|
||||
"lng_emoji_category0" = "Utilizzate di frequente";
|
||||
"lng_emoji_category0" = "Usate di frequente";
|
||||
"lng_emoji_category1" = "Persone";
|
||||
"lng_emoji_category2" = "Natura";
|
||||
"lng_emoji_category3" = "Cibo e bevande";
|
||||
|
@ -664,6 +671,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_emoji_category6" = "Oggetti";
|
||||
"lng_emoji_category7" = "Simboli e bandiere";
|
||||
|
||||
"lng_recent_stickers" = "Usati di frequente";
|
||||
"lng_switch_stickers" = "Sticker";
|
||||
"lng_switch_stickers_gifs" = "GIF e Sticker";
|
||||
"lng_switch_emoji" = "Emoji";
|
||||
|
@ -680,15 +688,20 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_stickers_add_pack" = "Aggiungi sticker";
|
||||
"lng_stickers_share_pack" = "Condividi sticker";
|
||||
"lng_stickers_not_found" = "Set di sticker non trovato.";
|
||||
"lng_stickers_packs_archived" = "Alcuni dei tuoi sticker non usati sono stati archiviati per fare spazio ai set che hai attivato.";
|
||||
"lng_stickers_archived" = "Sticker archiviati";
|
||||
"lng_stickers_copied" = "Link degli sticker copiato negli appunti.";
|
||||
"lng_stickers_default_set" = "Grandi menti";
|
||||
"lng_stickers_you_have" = "Organizza e riordina i set di sticker";
|
||||
"lng_stickers_packs" = "Set di sticker";
|
||||
"lng_stickers_reorder" = "Clicca e trascina per riordinare i set di sticker";
|
||||
"lng_stickers_featured" = "Sticker in primo piano";
|
||||
"lng_stickers_clear_recent" = "Cancella";
|
||||
"lng_stickers_clear_recent_sure" = "Sicuro di voler cancellare la lista degli sticker usati di recente?";
|
||||
"lng_stickers_remove" = "Elimina";
|
||||
"lng_stickers_return" = "Annulla";
|
||||
"lng_stickers_restore" = "Ripristina";
|
||||
"lng_stickers_count" = "{count:Caricamento...|# sticker|# sticker}";
|
||||
"lng_stickers_count" = "{count:Carico...|# sticker|# sticker}";
|
||||
|
||||
"lng_in_dlg_photo" = "Foto";
|
||||
"lng_in_dlg_video" = "File video";
|
||||
|
@ -704,9 +717,9 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_report_spam" = "Segnala spam";
|
||||
"lng_report_spam_hide" = "Nascondi";
|
||||
"lng_report_spam_thanks" = "Grazie per la tua segnalazione!";
|
||||
"lng_report_spam_sure" = "Sei sicuro di voler segnalare questo utente come spam?";
|
||||
"lng_report_spam_sure_group" = "Sei sicuro di voler segnalare dello spam in questo gruppo?";
|
||||
"lng_report_spam_sure_channel" = "Sei sicuro di voler segnalare dello spam in questo canale?";
|
||||
"lng_report_spam_sure" = "Sicuro di voler segnalare questo utente come spam?";
|
||||
"lng_report_spam_sure_group" = "Sicuro di voler segnalare dello spam in questo gruppo?";
|
||||
"lng_report_spam_sure_channel" = "Sicuro di voler segnalare dello spam in questo canale?";
|
||||
"lng_report_spam_ok" = "Segnala";
|
||||
"lng_cant_send_to_not_contact" = "Spiacenti, ma al momento puoi scrivere\nsolo ai contatti reciproci.\n{more_info}";
|
||||
"lng_cant_invite_not_contact" = "Spiacenti, ma al momento puoi aggiungere\nai gruppi solo contatti reciproci.\n{more_info}";
|
||||
|
@ -921,12 +934,19 @@ 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" = "— Il visualizzatore delle foto ora gestisce i cambi di risoluzione dello schermo correttamente\n— Risolto l'inoltro di foto via drag-n-drop\n— Miglioramenti di design e risoluzione di problemi";
|
||||
"lng_new_version_text" = "— Sticker in primo piano. Visualizza e installa set di sticker degni di nota dalla nuova scheda nelle Impostazioni.\n— Sticker archiviati. Gli sticker non utilizzati sono ora archiviati automaticamente quando vai oltre il limite dei 200 set.\n— Anteprime dei gruppi. Visualizza l'anteprima dei gruppi prima di unirti tramite link di invito - guarda chi è nel gruppo prima di unirti.\n— Nuovo lettore video interno.\n— Design migliorato per le chat.";
|
||||
|
||||
"lng_menu_insert_unicode" = "Inserisci carattere di controllo Unicode";
|
||||
|
||||
"lng_full_name" = "{first_name} {last_name}";
|
||||
|
||||
"lng_confirm_phone_link_invalid" = "Questo link non funziona o è scaduto.";
|
||||
"lng_confirm_phone_title" = "Annulla ripristino account";
|
||||
"lng_confirm_phone_about" = "Qualcuno con accesso al tuo numero di telefono {phone} ha richiesto l'eliminazione del tuo account Telegram e il ripristino della password della verifica in due passaggi.\n\nSe non sei stato tu, per favore inserisci il codice che abbiamo appena inviato come SMS al tuo numero.";
|
||||
"lng_confirm_phone_success" = "Fatto!\n\nIl processo di eliminazione per il tuo account {phone} è stato annullato. Puoi chiudere questa finestra ora.";
|
||||
"lng_confirm_phone_send" = "Invia";
|
||||
"lng_confirm_phone_enter_code" = "Per favore inserisci il codice.";
|
||||
|
||||
// Not used
|
||||
|
||||
"lng_topbar_info" = "Info";
|
||||
|
|
|
@ -190,6 +190,11 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_signin_reset_account" = "계정 초기화";
|
||||
"lng_signin_sure_reset" = "경고!\n\n계정 초기화 진행시 모든 대화,\n메시지 및 공유받은 미디어와 파일이 삭제가 됩니다.\n\n계정 초기화를 진행하시겠습니까?";
|
||||
"lng_signin_reset" = "초기화";
|
||||
"lng_signin_reset_wait" = "{phone_number} 계정이 사용중이고 비밀번호 설정이 되어져 있어, 보안을 위하여 1주일 이후에 삭제가 될 예정입니다. 이 설정은 언제든지 취소 할 수 있습니다.\n\n계정은 다음시간 이후에 초기화가 됩니다:\n{when}";
|
||||
"lng_signin_reset_in_days" = "{count_days:0 일|# 일|# 일} {count_hours:0 시간|# 시간|# 시간} {count_minutes:0 분|# 분|# 분}";
|
||||
"lng_signin_reset_in_hours" = "{count_hours:0 시간|# 시간|# 시간} {count_minutes:0 분|# 분|# 분}";
|
||||
"lng_signin_reset_in_minutes" = "{count_minutes:0 분|# 분|# 분}";
|
||||
"lng_signin_reset_cancelled" = "현재 사용중인 사용자가 요청하신 계정 초기화를 취소 하였습니다.\n7일 이후에 다시 시도해주세요.";
|
||||
|
||||
"lng_signup_title" = "개인정보 및 사진";
|
||||
"lng_signup_desc" = "이름을 입력해주시고,\n사진을 업로드해주세요.";
|
||||
|
@ -442,8 +447,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_profile_delete_and_exit" = "나가기";
|
||||
"lng_profile_kick" = "삭제";
|
||||
"lng_profile_admin" = "관리자";
|
||||
"lng_profile_sure_kick" = "{user}를 추방하시겠습니까?";
|
||||
"lng_profile_sure_kick_channel" = "{user}를 추방하시겠습니까?";
|
||||
"lng_profile_sure_kick" = "{user}를 내보내시겠습니까?";
|
||||
"lng_profile_sure_kick_channel" = "{user}를 내보내시겠습니까?";
|
||||
"lng_profile_sure_kick_admin" = "{user}를 관리자에서 제외 하시겠습니까?";
|
||||
"lng_profile_loading" = "로드중..";
|
||||
"lng_profile_shared_media" = "공유된 미디어";
|
||||
|
@ -544,7 +549,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_action_you_joined" = "채널에 입장하였습니다.";
|
||||
"lng_action_add_you_group" = "{from}님께서 그룹에 초대해주셨습니다.";
|
||||
"lng_action_you_joined_group" = "그룹방에 참여하였습니다.";
|
||||
"lng_action_kick_user" = "{from} 님께서 {user} 님을 추방하셨습니다.";
|
||||
"lng_action_kick_user" = "{from} 님께서 {user} 님을 내보내셨습니다.";
|
||||
"lng_action_user_left" = "{from} 님이 그룹을 나가셨습니다.";
|
||||
"lng_action_user_joined" = "{from} 님이 그룹에 들어오셨습니다.";
|
||||
"lng_action_user_joined_by_link" = "초대링크를 타고 {from} 님이 그룹에 참여하였습니다.";
|
||||
|
@ -602,6 +607,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_group_invite_want_join_channel" = "«{title}» 채널에 참여하시겠습니까?";
|
||||
"lng_group_invite_join" = "참여";
|
||||
|
||||
"lng_group_invite_members" = "{count:_not_used_|# 회원|# 회원}, 참여자 현황:";
|
||||
|
||||
"lng_group_invite_link" = "초대링크: ";
|
||||
"lng_group_invite_create" = "초대링크 생성";
|
||||
"lng_group_invite_about" = "이 링크를 통하여,\n그룹방에 초대가 가능합니다.";
|
||||
|
@ -664,6 +671,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_emoji_category6" = "물건";
|
||||
"lng_emoji_category7" = "심볼 및 국기";
|
||||
|
||||
"lng_recent_stickers" = "자주 사용";
|
||||
"lng_switch_stickers" = "스티커";
|
||||
"lng_switch_stickers_gifs" = "GIF & 스티커";
|
||||
"lng_switch_emoji" = "이모티콘";
|
||||
|
@ -680,11 +688,16 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_stickers_add_pack" = "스티커 추가";
|
||||
"lng_stickers_share_pack" = "스티커 공유";
|
||||
"lng_stickers_not_found" = "스티커 팩을 찾을 수 없습니다.";
|
||||
"lng_stickers_packs_archived" = "활성화된 스티커를 사용할 수 있는 공간이 필요하여 미사용 스티커중 일부는 보관되어집니다.";
|
||||
"lng_stickers_archived" = "보관된 스티커";
|
||||
"lng_stickers_copied" = "클립보드에 스티커 팩 링크가 복사 되었습니다.";
|
||||
"lng_stickers_default_set" = "Great Minds";
|
||||
"lng_stickers_you_have" = "스티커팩 관리 및 변경";
|
||||
"lng_stickers_packs" = "스티커팩";
|
||||
"lng_stickers_reorder" = "클릭과 드래그를 통하여 스태커 팩을 변경하세요";
|
||||
"lng_stickers_featured" = "인기 스티커";
|
||||
"lng_stickers_clear_recent" = "초기화";
|
||||
"lng_stickers_clear_recent_sure" = "자주 사용하는 스티커 리스트를 초기화 하겠습니까?";
|
||||
"lng_stickers_remove" = "삭제";
|
||||
"lng_stickers_return" = "실행취소";
|
||||
"lng_stickers_restore" = "복구";
|
||||
|
@ -921,12 +934,19 @@ 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" = "— 해상도 변경에 따라 사진 뷰어가 정상적으로 표시되도록 수정\n— 드래그앤드롭으로 사진 전송 수정\n— 디자인 및 버그 수정";
|
||||
"lng_new_version_text" = "— 인기스티커. 설정내 새로운 탭으로 인기있는 스티커를 확인하고 설치\n— 보관스티커. 스티커 개수가 200개 이상일 경우 미사용 스티커는 보관으로 자동이관\n— 그룹방 미리보기. 초대링크로 입장하기전에 미리보기 - 그룹방 참여인원 확인가능\n— 새로운 내부 비디오 플레이어\n— 대화 디자인 향상";
|
||||
|
||||
"lng_menu_insert_unicode" = "유니코드 문자를 입력하세요.";
|
||||
|
||||
"lng_full_name" = "{last_name} {first_name}";
|
||||
|
||||
"lng_confirm_phone_link_invalid" = "링크가 깨졌거나 폐기되었습니다.";
|
||||
"lng_confirm_phone_title" = "계정 초기화 취소";
|
||||
"lng_confirm_phone_about" = "{phone} 번호에 접근이 가능한 누군가가 해당 계정 및 2단계 비밀번호 초기화를 요청하였습니다.\n\n만약 본인이 아니실 경우 방금 보내드린 코드를 본인 번호에 SMS로 전송해주세요.";
|
||||
"lng_confirm_phone_success" = "성공!\n\n{phone} 계정에 대한 삭제 프로세스가 취소 되었습니다. 이창을 당으셔도 됩니다.";
|
||||
"lng_confirm_phone_send" = "보내기";
|
||||
"lng_confirm_phone_enter_code" = "코드를 입력해주세요";
|
||||
|
||||
// Not used
|
||||
|
||||
"lng_topbar_info" = "정보";
|
||||
|
|
|
@ -123,11 +123,11 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_server_error" = "Interne serverfout.";
|
||||
"lng_flood_error" = "Teveel pogingen. Probeer het later opnieuw.";
|
||||
"lng_gif_error" = "Er is iets een fout opgetreden bij het lezen van de GIF :(";
|
||||
"lng_edit_error" = "Je mag dit bericht niet bewerken";
|
||||
"lng_edit_error" = "Je mag dit bericht niet wijzigen";
|
||||
"lng_join_channel_error" = "Je bent lid van teveel kanalen of supergroepen, verlaat er wat om hier lid te worden.";
|
||||
"lng_edit_deleted" = "Bericht is gewist";
|
||||
"lng_edit_too_long" = "Je bericht is te lang";
|
||||
"lng_edit_message" = "Bericht bewerken";
|
||||
"lng_edit_message" = "Bericht wijzigen";
|
||||
"lng_edit_message_text" = "Nieuw bericht...";
|
||||
"lng_deleted" = "Onbekend";
|
||||
"lng_deleted_message" = "Verwijderd bericht";
|
||||
|
@ -190,6 +190,11 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_signin_reset_account" = "Account resetten";
|
||||
"lng_signin_sure_reset" = "Let op:\n\nAl je chats, berichten en alle andere data gaan verloren als je verder gaat!\n\nEcht je account resetten?";
|
||||
"lng_signin_reset" = "Reset";
|
||||
"lng_signin_reset_wait" = "Account {phone_number} is actief en beveiligd met een wachtwoord, het verwijderen stellen we daarom 1 week uit.\nJe kunt dit proces ieder moment annuleren.\n\nJe account kan gereset worden over:\n{when}";
|
||||
"lng_signin_reset_in_days" = "{count_days:0 dagen|# dag|# dagen} {count_hours:0 uur|# uur|# uur} {count_minutes:0 minuten|# minuut|# minuten}";
|
||||
"lng_signin_reset_in_hours" = "{count_hours:0 uur|# uur|# uur} {count_minutes:0 minuten|# minuut|# minuten}";
|
||||
"lng_signin_reset_in_minutes" = "{count_minutes:0 minuten|# minuut|# minuten}";
|
||||
"lng_signin_reset_cancelled" = "De actieve gebruiker heeft de recente poging om dit account te resetten geannuleerd. Probeer het over 7 dagen nog eens.";
|
||||
|
||||
"lng_signup_title" = "Informatie en foto";
|
||||
"lng_signup_desc" = "Voer je naam en\nupload een foto.";
|
||||
|
@ -602,6 +607,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_group_invite_want_join_channel" = "Wil je lid worden van het kanaal \"{title}\"?";
|
||||
"lng_group_invite_join" = "Lid worden";
|
||||
|
||||
"lng_group_invite_members" = "{count:_not_used_|# lid|# leden}, waaronder:";
|
||||
|
||||
"lng_group_invite_link" = "Uitnodigingslink:";
|
||||
"lng_group_invite_create" = "Uitnodigingslink maken";
|
||||
"lng_group_invite_about" = "Gebruikers kunnen lid worden\nvan je groep via deze link.";
|
||||
|
@ -618,9 +625,9 @@ 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_edited" = "gewijzigd";
|
||||
"lng_edited_date" = "Gewijzigd: {date}";
|
||||
"lng_cancel_edit_post_sure" = "Wijzigen annuleren?";
|
||||
"lng_cancel_edit_post_yes" = "Ja";
|
||||
"lng_cancel_edit_post_no" = "Nee";
|
||||
|
||||
|
@ -664,6 +671,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_emoji_category6" = "Objecten";
|
||||
"lng_emoji_category7" = "Symbolen en vlaggen";
|
||||
|
||||
"lng_recent_stickers" = "Veelgebruikt";
|
||||
"lng_switch_stickers" = "Stickers";
|
||||
"lng_switch_stickers_gifs" = "GIF's & stickers";
|
||||
"lng_switch_emoji" = "Emoji";
|
||||
|
@ -680,11 +688,16 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_stickers_add_pack" = "Stickers toevoegen";
|
||||
"lng_stickers_share_pack" = "Stickers delen";
|
||||
"lng_stickers_not_found" = "Stickerbundel niet gevonden.";
|
||||
"lng_stickers_packs_archived" = "Een aantal ongebruikte stickers zijn gearchiveerd om ruimte te maken voor je nieuwe stickerbundels";
|
||||
"lng_stickers_archived" = "Gearchiveerde stickers";
|
||||
"lng_stickers_copied" = "Stickerbundel-link gekopieerd naar klembord";
|
||||
"lng_stickers_default_set" = "Grote geesten";
|
||||
"lng_stickers_you_have" = "Beheer en sorteer stickerbundels";
|
||||
"lng_stickers_packs" = "Stickerbundels";
|
||||
"lng_stickers_reorder" = "Klik en sleep om stickerbundels te herschikken";
|
||||
"lng_stickers_featured" = "Populaire stickers";
|
||||
"lng_stickers_clear_recent" = "Wissen";
|
||||
"lng_stickers_clear_recent_sure" = "Lijst met veelgebruikte stickers echt wissen?";
|
||||
"lng_stickers_remove" = "Verwijder";
|
||||
"lng_stickers_return" = "Ongedaan maken";
|
||||
"lng_stickers_restore" = "Herstellen";
|
||||
|
@ -813,7 +826,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_context_save_gif" = "GIF opslaan";
|
||||
"lng_context_to_msg" = "Naar bericht gaan";
|
||||
"lng_context_reply_msg" = "Antwoord";
|
||||
"lng_context_edit_msg" = "Bewerken";
|
||||
"lng_context_edit_msg" = "Wijzig";
|
||||
"lng_context_forward_msg" = "Bericht doorsturen";
|
||||
"lng_context_delete_msg" = "Bericht verwijderen";
|
||||
"lng_context_select_msg" = "Bericht kiezen";
|
||||
|
@ -851,12 +864,12 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
|
||||
"lng_contact_phone" = "Telefoonnummer";
|
||||
"lng_enter_contact_data" = "Nieuw contact";
|
||||
"lng_edit_group_title" = "Groepsnaam bewerken";
|
||||
"lng_edit_contact_title" = "Naam bewerken";
|
||||
"lng_edit_group_title" = "Groepsnaam wijzigen";
|
||||
"lng_edit_contact_title" = "Naam wijzigen";
|
||||
"lng_edit_channel_title" = "Kanaal wijzigen";
|
||||
"lng_edit_sign_messages" = "Ondertekenen";
|
||||
"lng_edit_group" = "Groep bewerken";
|
||||
"lng_edit_self_title" = "Je naam bewerken";
|
||||
"lng_edit_group" = "Groep wijzigen";
|
||||
"lng_edit_self_title" = "Je naam wijzigen";
|
||||
"lng_confirm_contact_data" = "Nieuw contact";
|
||||
"lng_add_contact" = "Opslaan";
|
||||
"lng_add_contact_button" = "Nieuw contact";
|
||||
|
@ -921,12 +934,19 @@ 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" = "— Probleem met foto-weergave opgelost\n— Probleem met doorsturen via slepen opgelost\n— Diverse designverbeteringen en andere opgeloste problemen";
|
||||
"lng_new_version_text" = "— Populaire stickers. Zoek je nieuwe stickerbundels om te installeren? Populaire stickerbundels zijn nu te vinden onder instellingen.\n— Gearchiveerde stickers. Ongebruikte stickers archiveren we automatisch als je over de limiet van 200 stickerbundels gaat.\n— Voorvertoningen van groepen. Bekijk een groep voordat je besluit lid te worden.\n— Nieuwe ingebouwde videospeler.\n— Designverbeteringen voor de chatinterface.";
|
||||
|
||||
"lng_menu_insert_unicode" = "Unicode-besturingsteken invoegen";
|
||||
|
||||
"lng_full_name" = "{first_name} {last_name}";
|
||||
|
||||
"lng_confirm_phone_link_invalid" = "Deze link is defect of verlopen.";
|
||||
"lng_confirm_phone_title" = "Account reset annuleren";
|
||||
"lng_confirm_phone_about" = "Iemand met toegang tot nummer {phone} heeft geprobeerd om je Telegram account te verwijderen en je twee-staps-verificatie wachtwoord te resetten.\n\nAls jij dit niet was, geef dan de code in die we zojuist per SMS hebben gestuurd.";
|
||||
"lng_confirm_phone_success" = "De verwijdering van account: {phone} is geannuleerd.\nJe kunt dit venster nu sluiten.";
|
||||
"lng_confirm_phone_send" = "Stuur";
|
||||
"lng_confirm_phone_enter_code" = "Geef de code in.";
|
||||
|
||||
// Not used
|
||||
|
||||
"lng_topbar_info" = "Info";
|
||||
|
|
|
@ -178,7 +178,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_signin_password" = "Sua senha";
|
||||
"lng_signin_code" = "Código do e-mail";
|
||||
"lng_signin_recover" = "Esqueceu sua senha?";
|
||||
"lng_signin_recover_title" = "Resetar senha";
|
||||
"lng_signin_recover_title" = "Redefinir senha";
|
||||
"lng_signin_hint" = "Dica: {password_hint}";
|
||||
"lng_signin_recover_hint" = "Código enviado para {recover_email}";
|
||||
"lng_signin_bad_password" = "Você colocou uma senha errada.";
|
||||
|
@ -190,6 +190,11 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_signin_reset_account" = "Apagar sua conta";
|
||||
"lng_signin_sure_reset" = "Atenção!\n\nVocê perderá todos seus chats e mensagens, juntamente com quaisquer mídias e arquivos!\n\nVocê deseja apagar sua conta?";
|
||||
"lng_signin_reset" = "Apagar";
|
||||
"lng_signin_reset_wait" = "Uma vez que a conta {phone_number} está ativa e protegida por senha, nós iremos desativá-la em 1 semana, por questões de segurança. Você pode cancelar esse processo a qualquer momento.\n\nVocê poderá restaurar sua conta em:\n{when}";
|
||||
"lng_signin_reset_in_days" = "{count_days:0 dia|# dia|# dias} {count_hours:0 hora|# hora|# horas} {count_minutes:0 minuto|# minuto|# minutos}";
|
||||
"lng_signin_reset_in_hours" = "{count_hours:0 hora|# hora|# horas} {count_minutes:0 minuto|# minuto|# minutos}";
|
||||
"lng_signin_reset_in_minutes" = "{count_minutes:0 minuto|# minuto|# minutos}";
|
||||
"lng_signin_reset_cancelled" = "Suas tentativas recentes de restaurar essa conta foram canceladas pelo usuário ativo. Tente novamente em 7 dias.";
|
||||
|
||||
"lng_signup_title" = "Informação e foto";
|
||||
"lng_signup_desc" = "Por favor, insira nome e\ncarregue uma foto.";
|
||||
|
@ -214,7 +219,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_settings_uploading_photo" = "Carregando foto...";
|
||||
|
||||
"lng_username_title" = "Nome de usuário";
|
||||
"lng_username_about" = "Você pode escolher um nome de usuário no Telegram.\nAssim, outras pessoas poderão te encontrar\npelo nome de usuário e entrar em contato\nsem precisar saber seu telefone.\n\nVocê pode usar a-z, 0-9 e underline.\nO tamanho mínimo é 5 caracteres.";
|
||||
"lng_username_about" = "Você pode escolher um nome de usuário.\nAssim, outras pessoas poderão encontrar\nvocê pelo nome de usuário e entrar em\ncontato sem precisar saber seu telefone.\n\nVocê pode usar a-z, 0-9 e underline.\nO tamanho mínimo é de 5 caracteres.";
|
||||
"lng_username_choose" = "Escolha seu nome de usuário";
|
||||
"lng_username_invalid" = "Nome de usuário inválido.";
|
||||
"lng_username_occupied" = "Nome de usuário ocupado.";
|
||||
|
@ -602,6 +607,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_group_invite_want_join_channel" = "Você deseja entrar no canal «{title}»?";
|
||||
"lng_group_invite_join" = "Entrar";
|
||||
|
||||
"lng_group_invite_members" = "{count:_not_used_|# membro|# membros}, entre eles:";
|
||||
|
||||
"lng_group_invite_link" = "Link de convite:";
|
||||
"lng_group_invite_create" = "Criar um link de convite";
|
||||
"lng_group_invite_about" = "Usuários do Telegram poderão entrar\nem seu grupo clicando no link.";
|
||||
|
@ -655,7 +662,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_media_auto_groups" = "Grupos e canais";
|
||||
"lng_media_auto_play" = "Reproduzir automaticamente";
|
||||
|
||||
"lng_emoji_category0" = "Frequentemente usado";
|
||||
"lng_emoji_category0" = "Usados frequentemente";
|
||||
"lng_emoji_category1" = "Pessoas";
|
||||
"lng_emoji_category2" = "Natureza";
|
||||
"lng_emoji_category3" = "Comidas e Bebidas";
|
||||
|
@ -664,6 +671,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_emoji_category6" = "Objetos";
|
||||
"lng_emoji_category7" = "Símbolos e Bandeiras";
|
||||
|
||||
"lng_recent_stickers" = "Usados frequentemente";
|
||||
"lng_switch_stickers" = "Stickers";
|
||||
"lng_switch_stickers_gifs" = "GIFs e Stickers";
|
||||
"lng_switch_emoji" = "Emoji";
|
||||
|
@ -680,11 +688,16 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_stickers_add_pack" = "Adicionar aos Stickers";
|
||||
"lng_stickers_share_pack" = "Compartilhar Stickers";
|
||||
"lng_stickers_not_found" = "Pacote de sticker não encontrado.";
|
||||
"lng_stickers_packs_archived" = "Alguns de seus stickers não usados foram arquivados para dar espaço aos pacotes que você ativou.";
|
||||
"lng_stickers_archived" = "Stickers Arquivados";
|
||||
"lng_stickers_copied" = "Link copiado para a área de transferência.";
|
||||
"lng_stickers_default_set" = "Grandes Mentes";
|
||||
"lng_stickers_you_have" = "Gerenciar e reordenar os pacotes de sticker";
|
||||
"lng_stickers_packs" = "Pacotes de Sticker";
|
||||
"lng_stickers_reorder" = "Clique e arraste para reordenar os pacotes";
|
||||
"lng_stickers_featured" = "Stickers Populares";
|
||||
"lng_stickers_clear_recent" = "Limpar";
|
||||
"lng_stickers_clear_recent_sure" = "Você tem certeza que deseja limpar seu histórico de stickers frequentes?";
|
||||
"lng_stickers_remove" = "Remover";
|
||||
"lng_stickers_return" = "Desfazer";
|
||||
"lng_stickers_restore" = "Restaurar";
|
||||
|
@ -921,12 +934,19 @@ 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" = "— O visualizador de fotos agora maneja corretamente a mudança de resolução da tela\n— O bug ao encaminhar fotos arrastando e soltando foi resolvido. \n— Várias melhorias de design e outras resoluções de bugs";
|
||||
"lng_new_version_text" = "— Stickers populares. Veja e instale pacotes de stickers em destaque através da nova aba em Configurações.\n— Stickers arquivados. Stickers sem uso agora são arquivados automaticamente quando você chegar ao limite dos 200.\n— Pré-visualização de grupos. Visualize os grupos antes de entrar através do link de convite - veja quem mais está no grupo antes de entrar.\n— Novo reprodutor de vídeo interno.\n— Design melhorado nas conversas.";
|
||||
|
||||
"lng_menu_insert_unicode" = "Inserir caractere de controle Unicode";
|
||||
|
||||
"lng_full_name" = "{first_name} {last_name}";
|
||||
|
||||
"lng_confirm_phone_link_invalid" = "Esse link está quebrado ou expirado.";
|
||||
"lng_confirm_phone_title" = "Cancelar exclusão da conta";
|
||||
"lng_confirm_phone_about" = "Alguém com acesso ao seu número de telefone {phone} solicitou a exclusão de sua conta do Telegram e redefiniu sua senha de Verificação em Duas Etapas.\n\nSe não foi você, por favor insira o código que te enviamos via SMS.";
|
||||
"lng_confirm_phone_success" = "Sucesso!\n\nO processo de exclusão foi cancelado em sua conta {phone}. Você pode fechar essa janela agora.";
|
||||
"lng_confirm_phone_send" = "Enviar";
|
||||
"lng_confirm_phone_enter_code" = "Por favor, insira o código.";
|
||||
|
||||
// Not used
|
||||
|
||||
"lng_topbar_info" = "Info";
|
||||
|
|
|
@ -34,8 +34,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
|
|||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 0,9,57,0
|
||||
PRODUCTVERSION 0,9,57,0
|
||||
FILEVERSION 0,10,0,0
|
||||
PRODUCTVERSION 0,10,0,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
|
@ -51,10 +51,10 @@ BEGIN
|
|||
BLOCK "040904b0"
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Telegram Messenger LLP"
|
||||
VALUE "FileVersion", "0.9.57.0"
|
||||
VALUE "FileVersion", "0.10.0.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2016"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "0.9.57.0"
|
||||
VALUE "ProductVersion", "0.10.0.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
|
|
@ -25,8 +25,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
|||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 0,9,57,0
|
||||
PRODUCTVERSION 0,9,57,0
|
||||
FILEVERSION 0,10,0,0
|
||||
PRODUCTVERSION 0,10,0,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.57.0"
|
||||
VALUE "FileVersion", "0.10.0.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2016"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "0.9.57.0"
|
||||
VALUE "ProductVersion", "0.10.0.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
|
|
@ -543,7 +543,6 @@ void ApiWrap::lastParticipantsDone(ChannelData *peer, const MTPchannels_ChannelP
|
|||
}
|
||||
if (!keyboardBotFound) {
|
||||
h->clearLastKeyboard();
|
||||
if (App::main()) App::main()->updateBotKeyboard(h);
|
||||
}
|
||||
int newMembersCount = qMax(d.vcount.v, v.count());
|
||||
if (newMembersCount > peer->membersCount()) {
|
||||
|
@ -924,12 +923,12 @@ void ApiWrap::gotStickerSet(uint64 setId, const MTPmessages_StickerSet &result)
|
|||
_stickerSetRequests.remove(setId);
|
||||
|
||||
if (result.type() != mtpc_messages_stickerSet) return;
|
||||
const auto &d(result.c_messages_stickerSet());
|
||||
auto &d(result.c_messages_stickerSet());
|
||||
|
||||
if (d.vset.type() != mtpc_stickerSet) return;
|
||||
const auto &s(d.vset.c_stickerSet());
|
||||
auto &s(d.vset.c_stickerSet());
|
||||
|
||||
Stickers::Sets &sets(Global::RefStickerSets());
|
||||
auto &sets = Global::RefStickerSets();
|
||||
auto it = sets.find(setId);
|
||||
if (it == sets.cend()) return;
|
||||
|
||||
|
@ -937,7 +936,9 @@ void ApiWrap::gotStickerSet(uint64 setId, const MTPmessages_StickerSet &result)
|
|||
it->hash = s.vhash.v;
|
||||
it->shortName = qs(s.vshort_name);
|
||||
it->title = stickerSetTitle(s);
|
||||
it->flags = s.vflags.v;
|
||||
auto clientFlags = it->flags & (MTPDstickerSet_ClientFlag::f_featured | MTPDstickerSet_ClientFlag::f_unread | MTPDstickerSet_ClientFlag::f_not_loaded | MTPDstickerSet_ClientFlag::f_special);
|
||||
it->flags = s.vflags.v | clientFlags;
|
||||
it->flags &= ~MTPDstickerSet_ClientFlag::f_not_loaded;
|
||||
|
||||
const auto &d_docs(d.vdocuments.c_vector().v);
|
||||
auto custom = sets.find(Stickers::CustomSetId);
|
||||
|
@ -1003,7 +1004,14 @@ void ApiWrap::gotStickerSet(uint64 setId, const MTPmessages_StickerSet &result)
|
|||
Local::writeUserSettings();
|
||||
}
|
||||
|
||||
Local::writeStickers();
|
||||
if (it->flags & MTPDstickerSet::Flag::f_installed) {
|
||||
if (!(it->flags & MTPDstickerSet::Flag::f_archived)) {
|
||||
Local::writeInstalledStickers();
|
||||
}
|
||||
}
|
||||
if (it->flags & MTPDstickerSet_ClientFlag::f_featured) {
|
||||
Local::writeFeaturedStickers();
|
||||
}
|
||||
|
||||
if (App::main()) emit App::main()->stickersUpdated();
|
||||
}
|
||||
|
|
|
@ -37,7 +37,7 @@ typedef QHash<PhotoData*, HistoryItemsMap> PhotoItems;
|
|||
typedef QHash<DocumentData*, HistoryItemsMap> DocumentItems;
|
||||
typedef QHash<WebPageData*, HistoryItemsMap> WebPageItems;
|
||||
typedef QHash<int32, HistoryItemsMap> SharedContactItems;
|
||||
typedef QHash<ClipReader*, HistoryItem*> GifItems;
|
||||
typedef QHash<Media::Clip::Reader*, HistoryItem*> GifItems;
|
||||
|
||||
typedef QHash<PhotoId, PhotoData*> PhotosData;
|
||||
typedef QHash<DocumentId, DocumentData*> DocumentsData;
|
||||
|
@ -65,7 +65,9 @@ namespace App {
|
|||
bool onlineColorUse(UserData *user, TimeId now);
|
||||
bool onlineColorUse(TimeId online, TimeId now);
|
||||
|
||||
UserData *feedUser(const MTPUser &user);
|
||||
UserData *feedUsers(const MTPVector<MTPUser> &users); // returns last user
|
||||
PeerData *feedChat(const MTPChat &chat);
|
||||
PeerData *feedChats(const MTPVector<MTPChat> &chats); // returns last chat
|
||||
|
||||
void feedParticipants(const MTPChatParticipants &p, bool requestBotInfos, bool emitPeerUpdated = true);
|
||||
|
@ -149,7 +151,7 @@ namespace App {
|
|||
PhotoData *photo(const PhotoId &photo);
|
||||
PhotoData *photoSet(const PhotoId &photo, PhotoData *convert, const uint64 &access, int32 date, const ImagePtr &thumb, const ImagePtr &medium, const ImagePtr &full);
|
||||
DocumentData *document(const DocumentId &document);
|
||||
DocumentData *documentSet(const DocumentId &document, DocumentData *convert, const uint64 &access, int32 date, const QVector<MTPDocumentAttribute> &attributes, const QString &mime, const ImagePtr &thumb, int32 dc, int32 size, const StorageImageLocation &thumbLocation);
|
||||
DocumentData *documentSet(const DocumentId &document, DocumentData *convert, const uint64 &access, int32 version, int32 date, const QVector<MTPDocumentAttribute> &attributes, const QString &mime, const ImagePtr &thumb, int32 dc, int32 size, const StorageImageLocation &thumbLocation);
|
||||
WebPageData *webPage(const WebPageId &webPage);
|
||||
WebPageData *webPageSet(const WebPageId &webPage, WebPageData *convert, const QString &, const QString &url, const QString &displayUrl, const QString &siteName, const QString &title, const QString &description, PhotoData *photo, DocumentData *doc, int32 duration, const QString &author, int32 pendingTill);
|
||||
LocationData *location(const LocationCoords &coords);
|
||||
|
@ -255,8 +257,8 @@ namespace App {
|
|||
const SharedContactItems &sharedContactItems();
|
||||
QString phoneFromSharedContact(int32 userId);
|
||||
|
||||
void regGifItem(ClipReader *reader, HistoryItem *item);
|
||||
void unregGifItem(ClipReader *reader);
|
||||
void regGifItem(Media::Clip::Reader *reader, HistoryItem *item);
|
||||
void unregGifItem(Media::Clip::Reader *reader);
|
||||
void stopGifItems();
|
||||
|
||||
void regMuted(PeerData *peer, int32 changeIn);
|
||||
|
|
|
@ -264,7 +264,7 @@ void Application::readClients() {
|
|||
}
|
||||
} else if (cmd.startsWith(qsl("OPEN:"))) {
|
||||
if (cStartUrl().isEmpty()) {
|
||||
startUrl = _escapeFrom7bit(cmds.mid(from + 5, to - from - 5));
|
||||
startUrl = _escapeFrom7bit(cmds.mid(from + 5, to - from - 5)).mid(0, 8192);
|
||||
}
|
||||
} else {
|
||||
LOG(("Application Error: unknown command %1 passed in local socket").arg(QString(cmd.constData(), cmd.length())));
|
||||
|
@ -533,6 +533,12 @@ namespace Sandbox {
|
|||
}
|
||||
}
|
||||
|
||||
void removeEventFilter(QObject *filter) {
|
||||
if (Application *a = application()) {
|
||||
a->removeEventFilter(filter);
|
||||
}
|
||||
}
|
||||
|
||||
void execExternal(const QString &cmd) {
|
||||
DEBUG_LOG(("Application Info: executing external command '%1'").arg(cmd));
|
||||
if (cmd == "show") {
|
||||
|
@ -1008,11 +1014,11 @@ void AppClass::uploadProfilePhoto(const QImage &tosend, const PeerId &peerId) {
|
|||
PreparedPhotoThumbs photoThumbs;
|
||||
QVector<MTPPhotoSize> photoSizes;
|
||||
|
||||
QPixmap thumb = QPixmap::fromImage(tosend.scaled(160, 160, Qt::KeepAspectRatio, Qt::SmoothTransformation), Qt::ColorOnly);
|
||||
QPixmap thumb = App::pixmapFromImageInPlace(tosend.scaled(160, 160, Qt::KeepAspectRatio, Qt::SmoothTransformation));
|
||||
photoThumbs.insert('a', thumb);
|
||||
photoSizes.push_back(MTP_photoSize(MTP_string("a"), MTP_fileLocationUnavailable(MTP_long(0), MTP_int(0), MTP_long(0)), MTP_int(thumb.width()), MTP_int(thumb.height()), MTP_int(0)));
|
||||
|
||||
QPixmap medium = QPixmap::fromImage(tosend.scaled(320, 320, Qt::KeepAspectRatio, Qt::SmoothTransformation), Qt::ColorOnly);
|
||||
QPixmap medium = App::pixmapFromImageInPlace(tosend.scaled(320, 320, Qt::KeepAspectRatio, Qt::SmoothTransformation));
|
||||
photoThumbs.insert('b', medium);
|
||||
photoSizes.push_back(MTP_photoSize(MTP_string("b"), MTP_fileLocationUnavailable(MTP_long(0), MTP_int(0), MTP_long(0)), MTP_int(medium.width()), MTP_int(medium.height()), MTP_int(0)));
|
||||
|
||||
|
@ -1045,14 +1051,9 @@ void AppClass::checkMapVersion() {
|
|||
if (Local::oldMapVersion() < AppVersion) {
|
||||
if (Local::oldMapVersion()) {
|
||||
QString versionFeatures;
|
||||
if ((cAlphaVersion() || cBetaVersion()) && Local::oldMapVersion() < 9057) {
|
||||
#if defined Q_OS_LINUX32 || defined Q_OS_LINUX64
|
||||
versionFeatures = QString::fromUtf8("\xe2\x80\x94 Design improvements\n\xe2\x80\x94 Linux : trying to use GTK file chooser when it is available");
|
||||
#else // Q_OS_LINUX32 || Q_OS_LINUX64
|
||||
versionFeatures = QString::fromUtf8("\xe2\x80\x94 Design improvements");
|
||||
#endif // Q_OS_LINUX32 || Q_OS_LINUX64
|
||||
// versionFeatures = langNewVersionText();
|
||||
} else if (Local::oldMapVersion() < 9056) {
|
||||
if ((cAlphaVersion() || cBetaVersion()) && Local::oldMapVersion() < 9058) {
|
||||
versionFeatures = QString::fromUtf8("\xe2\x80\x94 Alpha version of an embedded video player");
|
||||
} else if (Local::oldMapVersion() < 10000) {
|
||||
versionFeatures = langNewVersionText();
|
||||
} else {
|
||||
versionFeatures = lang(lng_new_version_minor).trimmed();
|
||||
|
|
|
@ -114,6 +114,7 @@ namespace Sandbox {
|
|||
bool isSavingSession();
|
||||
|
||||
void installEventFilter(QObject *filter);
|
||||
void removeEventFilter(QObject *filter);
|
||||
|
||||
void execExternal(const QString &cmd);
|
||||
|
||||
|
|
|
@ -1,363 +0,0 @@
|
|||
/*
|
||||
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 "core/basic_types.h"
|
||||
|
||||
void audioInit();
|
||||
bool audioWorks();
|
||||
void audioPlayNotify();
|
||||
void audioFinish();
|
||||
|
||||
enum AudioPlayerState {
|
||||
AudioPlayerStopped = 0x01,
|
||||
AudioPlayerStoppedAtEnd = 0x02,
|
||||
AudioPlayerStoppedAtError = 0x03,
|
||||
AudioPlayerStoppedAtStart = 0x04,
|
||||
AudioPlayerStoppedMask = 0x07,
|
||||
|
||||
AudioPlayerStarting = 0x08,
|
||||
AudioPlayerPlaying = 0x10,
|
||||
AudioPlayerFinishing = 0x18,
|
||||
AudioPlayerPausing = 0x20,
|
||||
AudioPlayerPaused = 0x28,
|
||||
AudioPlayerPausedAtEnd = 0x30,
|
||||
AudioPlayerResuming = 0x38,
|
||||
};
|
||||
|
||||
class AudioPlayerFader;
|
||||
class AudioPlayerLoaders;
|
||||
|
||||
class AudioPlayer : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
AudioPlayer();
|
||||
|
||||
void play(const AudioMsgId &audio, int64 position = 0);
|
||||
void play(const SongMsgId &song, int64 position = 0);
|
||||
void pauseresume(MediaOverviewType type, bool fast = false);
|
||||
void seek(int64 position); // type == OverviewFiles
|
||||
void stop(MediaOverviewType type);
|
||||
|
||||
void stopAndClear();
|
||||
|
||||
void currentState(AudioMsgId *audio, AudioPlayerState *state = 0, int64 *position = 0, int64 *duration = 0, int32 *frequency = 0);
|
||||
void currentState(SongMsgId *song, AudioPlayerState *state = 0, int64 *position = 0, int64 *duration = 0, int32 *frequency = 0);
|
||||
|
||||
void clearStoppedAtStart(const AudioMsgId &audio);
|
||||
void clearStoppedAtStart(const SongMsgId &song);
|
||||
|
||||
void resumeDevice();
|
||||
|
||||
~AudioPlayer();
|
||||
|
||||
public slots:
|
||||
|
||||
void onError(const AudioMsgId &audio);
|
||||
void onError(const SongMsgId &song);
|
||||
|
||||
void onStopped(const AudioMsgId &audio);
|
||||
void onStopped(const SongMsgId &song);
|
||||
|
||||
signals:
|
||||
|
||||
void updated(const AudioMsgId &audio);
|
||||
void updated(const SongMsgId &song);
|
||||
|
||||
void stopped(const AudioMsgId &audio);
|
||||
void stopped(const SongMsgId &song);
|
||||
|
||||
void stoppedOnError(const AudioMsgId &audio);
|
||||
void stoppedOnError(const SongMsgId &song);
|
||||
|
||||
void loaderOnStart(const AudioMsgId &audio, qint64 position);
|
||||
void loaderOnStart(const SongMsgId &song, qint64 position);
|
||||
|
||||
void loaderOnCancel(const AudioMsgId &audio);
|
||||
void loaderOnCancel(const SongMsgId &song);
|
||||
|
||||
void faderOnTimer();
|
||||
|
||||
void suppressSong();
|
||||
void unsuppressSong();
|
||||
void suppressAll();
|
||||
|
||||
void songVolumeChanged();
|
||||
|
||||
private:
|
||||
|
||||
bool fadedStop(MediaOverviewType type, bool *fadedStart = 0);
|
||||
bool updateCurrentStarted(MediaOverviewType type, int32 pos = -1);
|
||||
bool checkCurrentALError(MediaOverviewType type);
|
||||
|
||||
struct Msg {
|
||||
Msg() : position(0)
|
||||
, duration(0)
|
||||
, frequency(AudioVoiceMsgFrequency)
|
||||
, skipStart(0)
|
||||
, skipEnd(0)
|
||||
, loading(false)
|
||||
, started(0)
|
||||
, state(AudioPlayerStopped)
|
||||
, source(0)
|
||||
, nextBuffer(0) {
|
||||
memset(buffers, 0, sizeof(buffers));
|
||||
memset(samplesCount, 0, sizeof(samplesCount));
|
||||
}
|
||||
|
||||
void clearData();
|
||||
|
||||
FileLocation file;
|
||||
QByteArray data;
|
||||
int64 position, duration;
|
||||
int32 frequency;
|
||||
int64 skipStart, skipEnd;
|
||||
bool loading;
|
||||
int64 started;
|
||||
AudioPlayerState state;
|
||||
|
||||
uint32 source;
|
||||
int32 nextBuffer;
|
||||
uint32 buffers[3];
|
||||
int64 samplesCount[3];
|
||||
};
|
||||
struct AudioMsg : public Msg {
|
||||
AudioMsg() {
|
||||
}
|
||||
void clear() {
|
||||
audio = AudioMsgId();
|
||||
Msg::clearData();
|
||||
}
|
||||
AudioMsgId audio;
|
||||
};
|
||||
struct SongMsg : public Msg {
|
||||
SongMsg() {
|
||||
}
|
||||
void clear() {
|
||||
song = SongMsgId();
|
||||
Msg::clearData();
|
||||
}
|
||||
SongMsgId song;
|
||||
};
|
||||
|
||||
void currentState(Msg *current, AudioPlayerState *state, int64 *position, int64 *duration, int32 *frequency);
|
||||
void setStoppedState(Msg *current, AudioPlayerState state = AudioPlayerStopped);
|
||||
|
||||
int32 _audioCurrent;
|
||||
AudioMsg _audioData[AudioVoiceMsgSimultaneously];
|
||||
|
||||
int32 _songCurrent;
|
||||
SongMsg _songData[AudioSongSimultaneously];
|
||||
|
||||
QMutex _mutex;
|
||||
|
||||
friend class AudioPlayerFader;
|
||||
friend class AudioPlayerLoaders;
|
||||
|
||||
QThread _faderThread, _loaderThread;
|
||||
AudioPlayerFader *_fader;
|
||||
AudioPlayerLoaders *_loader;
|
||||
|
||||
};
|
||||
|
||||
class AudioCaptureInner;
|
||||
|
||||
class AudioCapture : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
AudioCapture();
|
||||
|
||||
void start();
|
||||
void stop(bool needResult);
|
||||
|
||||
bool check();
|
||||
|
||||
~AudioCapture();
|
||||
|
||||
signals:
|
||||
|
||||
void captureOnStart();
|
||||
void captureOnStop(bool needResult);
|
||||
|
||||
void onDone(QByteArray data, VoiceWaveform waveform, qint32 samples);
|
||||
void onUpdate(quint16 level, qint32 samples);
|
||||
void onError();
|
||||
|
||||
private:
|
||||
|
||||
friend class AudioCaptureInner;
|
||||
|
||||
QThread _captureThread;
|
||||
AudioCaptureInner *_capture;
|
||||
|
||||
};
|
||||
|
||||
AudioPlayer *audioPlayer();
|
||||
AudioCapture *audioCapture();
|
||||
|
||||
class AudioPlayerFader : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
AudioPlayerFader(QThread *thread);
|
||||
void resumeDevice();
|
||||
|
||||
signals:
|
||||
|
||||
void error(const AudioMsgId &audio);
|
||||
void error(const SongMsgId &audio);
|
||||
void playPositionUpdated(const AudioMsgId &audio);
|
||||
void playPositionUpdated(const SongMsgId &audio);
|
||||
void audioStopped(const AudioMsgId &audio);
|
||||
void audioStopped(const SongMsgId &audio);
|
||||
void needToPreload(const AudioMsgId &audio);
|
||||
void needToPreload(const SongMsgId &audio);
|
||||
|
||||
void stopPauseDevice();
|
||||
|
||||
public slots:
|
||||
|
||||
void onInit();
|
||||
void onTimer();
|
||||
void onPauseTimer();
|
||||
void onPauseTimerStop();
|
||||
|
||||
void onSuppressSong();
|
||||
void onUnsuppressSong();
|
||||
void onSuppressAll();
|
||||
void onSongVolumeChanged();
|
||||
|
||||
private:
|
||||
|
||||
enum {
|
||||
EmitError = 0x01,
|
||||
EmitStopped = 0x02,
|
||||
EmitPositionUpdated = 0x04,
|
||||
EmitNeedToPreload = 0x08,
|
||||
};
|
||||
int32 updateOnePlayback(AudioPlayer::Msg *m, bool &hasPlaying, bool &hasFading, float64 suppressGain, bool suppressGainChanged);
|
||||
void setStoppedState(AudioPlayer::Msg *m, AudioPlayerState state = AudioPlayerStopped);
|
||||
|
||||
QTimer _timer, _pauseTimer;
|
||||
QMutex _pauseMutex;
|
||||
bool _pauseFlag, _paused;
|
||||
|
||||
bool _suppressAll, _suppressAllAnim, _suppressSong, _suppressSongAnim, _songVolumeChanged;
|
||||
anim::fvalue _suppressAllGain, _suppressSongGain;
|
||||
uint64 _suppressAllStart, _suppressSongStart;
|
||||
|
||||
};
|
||||
|
||||
class AudioPlayerLoader;
|
||||
class AudioPlayerLoaders : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
AudioPlayerLoaders(QThread *thread);
|
||||
~AudioPlayerLoaders();
|
||||
|
||||
signals:
|
||||
|
||||
void error(const AudioMsgId &audio);
|
||||
void error(const SongMsgId &song);
|
||||
void needToCheck();
|
||||
|
||||
public slots:
|
||||
|
||||
void onInit();
|
||||
|
||||
void onStart(const AudioMsgId &audio, qint64 position);
|
||||
void onStart(const SongMsgId &audio, qint64 position);
|
||||
|
||||
void onLoad(const AudioMsgId &audio);
|
||||
void onLoad(const SongMsgId &audio);
|
||||
|
||||
void onCancel(const AudioMsgId &audio);
|
||||
void onCancel(const SongMsgId &audio);
|
||||
|
||||
private:
|
||||
|
||||
AudioMsgId _audio;
|
||||
AudioPlayerLoader *_audioLoader;
|
||||
|
||||
SongMsgId _song;
|
||||
AudioPlayerLoader *_songLoader;
|
||||
|
||||
void emitError(MediaOverviewType type);
|
||||
void clear(MediaOverviewType type);
|
||||
void setStoppedState(AudioPlayer::Msg *m, AudioPlayerState state = AudioPlayerStopped);
|
||||
AudioMsgId clearAudio();
|
||||
SongMsgId clearSong();
|
||||
|
||||
enum SetupError {
|
||||
SetupErrorAtStart = 0,
|
||||
SetupErrorNotPlaying = 1,
|
||||
SetupErrorLoadedFull = 2,
|
||||
SetupNoErrorStarted = 3,
|
||||
};
|
||||
void loadData(MediaOverviewType type, const void *objId, qint64 position);
|
||||
AudioPlayerLoader *setupLoader(MediaOverviewType type, const void *objId, SetupError &err, qint64 position);
|
||||
AudioPlayer::Msg *checkLoader(MediaOverviewType type);
|
||||
|
||||
};
|
||||
|
||||
struct AudioCapturePrivate;
|
||||
|
||||
class AudioCaptureInner : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
AudioCaptureInner(QThread *thread);
|
||||
~AudioCaptureInner();
|
||||
|
||||
signals:
|
||||
|
||||
void error();
|
||||
void update(quint16 level, qint32 samples);
|
||||
void done(QByteArray data, VoiceWaveform waveform, qint32 samples);
|
||||
|
||||
public slots:
|
||||
|
||||
void onInit();
|
||||
void onStart();
|
||||
void onStop(bool needResult);
|
||||
|
||||
void onTimeout();
|
||||
|
||||
private:
|
||||
|
||||
void writeFrame(int32 offset, int32 framesize);
|
||||
|
||||
AudioCapturePrivate *d;
|
||||
QTimer _timer;
|
||||
QByteArray _captured;
|
||||
|
||||
};
|
||||
|
||||
MTPDocumentAttribute audioReadSongAttributes(const QString &fname, const QByteArray &data, QImage &cover, QByteArray &coverBytes, QByteArray &coverFormat);
|
||||
VoiceWaveform audioCountWaveform(const FileLocation &file, const QByteArray &data);
|
|
@ -595,7 +595,7 @@ void GroupInfoBox::onPhoto() {
|
|||
|
||||
void GroupInfoBox::onPhotoReady(const QImage &img) {
|
||||
_photoBig = img;
|
||||
_photoSmall = QPixmap::fromImage(img.scaled(st::newGroupPhotoSize * cIntRetinaFactor(), st::newGroupPhotoSize * cIntRetinaFactor(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation), Qt::ColorOnly);
|
||||
_photoSmall = App::pixmapFromImageInPlace(img.scaled(st::newGroupPhotoSize * cIntRetinaFactor(), st::newGroupPhotoSize * cIntRetinaFactor(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
|
||||
_photoSmall.setDevicePixelRatio(cRetinaFactor());
|
||||
}
|
||||
|
||||
|
|
75
Telegram/SourceFiles/boxes/boxes.style
Normal file
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
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
|
||||
*/
|
||||
using "basic.style";
|
||||
|
||||
confirmInviteTitle: flatLabel(labelDefFlat) {
|
||||
font: font(16px semibold);
|
||||
align: align(center);
|
||||
width: 320px;
|
||||
maxHeight: 24px;
|
||||
textFg: #333333;
|
||||
}
|
||||
confirmInviteStatus: flatLabel(labelDefFlat) {
|
||||
font: font(boxFontSize);
|
||||
align: align(center);
|
||||
width: 320px;
|
||||
maxHeight: 20px;
|
||||
textFg: windowSubTextFg;
|
||||
}
|
||||
confirmInviteTitleTop: 106px;
|
||||
confirmInvitePhotoSize: 76px;
|
||||
confirmInvitePhotoTop: 20px;
|
||||
confirmInviteStatusTop: 136px;
|
||||
confirmInviteUserHeight: 80px;
|
||||
confirmInviteUserPhotoSize: 56px;
|
||||
confirmInviteUserPhotoTop: 166px;
|
||||
confirmInviteUserName: flatLabel(labelDefFlat) {
|
||||
font: normalFont;
|
||||
align: align(center);
|
||||
width: 66px;
|
||||
maxHeight: 20px;
|
||||
}
|
||||
confirmInviteUserNameTop: 227px;
|
||||
|
||||
stickersAddIcon: icon {
|
||||
{ "stickers_add", #ffffff },
|
||||
};
|
||||
stickersAddSize: size(30px, 24px);
|
||||
|
||||
stickersFeaturedHeight: 32px;
|
||||
stickersFeaturedFont: contactsNameFont;
|
||||
stickersFeaturedPosition: point(16px, 6px);
|
||||
stickersFeaturedBadgeFont: semiboldFont;
|
||||
stickersFeaturedBadgeSize: 21px;
|
||||
stickersFeaturedPen: contactsNewItemFg;
|
||||
stickersFeaturedUnreadBg: msgFileInBg;
|
||||
stickersFeaturedUnreadSize: 5px;
|
||||
stickersFeaturedUnreadSkip: 5px;
|
||||
stickersFeaturedUnreadTop: 7px;
|
||||
stickersFeaturedInstalled: icon {
|
||||
{ "mediaview_save_check", #40ace3 }
|
||||
};
|
||||
|
||||
confirmPhoneAboutLabel: flatLabel(labelDefFlat) {
|
||||
width: 282px;
|
||||
}
|
||||
confirmPhoneCodeField: InputField(defaultInputField) {
|
||||
}
|
|
@ -27,6 +27,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
#include "apiwrap.h"
|
||||
#include "application.h"
|
||||
#include "core/click_handler_types.h"
|
||||
#include "styles/style_boxes.h"
|
||||
|
||||
TextParseOptions _confirmBoxTextOptions = {
|
||||
TextParseLinks | TextParseMultiline | TextParseRichText, // flags
|
||||
|
@ -520,3 +521,86 @@ void KickMemberBox::onConfirm() {
|
|||
App::api()->kickParticipant(channel, _member);
|
||||
}
|
||||
}
|
||||
|
||||
ConfirmInviteBox::ConfirmInviteBox(const QString &title, const MTPChatPhoto &photo, int count, const QVector<UserData*> &participants) : AbstractBox()
|
||||
, _title(this, st::confirmInviteTitle)
|
||||
, _status(this, st::confirmInviteStatus)
|
||||
, _photo(chatDefPhoto(0))
|
||||
, _participants(participants)
|
||||
, _join(this, lang(lng_group_invite_join), st::defaultBoxButton)
|
||||
, _cancel(this, lang(lng_cancel), st::cancelBoxButton) {
|
||||
if (_participants.size() > 4) {
|
||||
_participants.resize(4);
|
||||
}
|
||||
|
||||
_title->setText(title);
|
||||
QString status;
|
||||
if (_participants.isEmpty() || _participants.size() >= count) {
|
||||
status = lng_chat_status_members(lt_count, count);
|
||||
} else {
|
||||
status = lng_group_invite_members(lt_count, count);
|
||||
}
|
||||
_status->setText(status);
|
||||
if (photo.type() == mtpc_chatPhoto) {
|
||||
auto &d = photo.c_chatPhoto();
|
||||
auto location = App::imageLocation(160, 160, d.vphoto_small);
|
||||
if (!location.isNull()) {
|
||||
_photo = ImagePtr(location);
|
||||
if (!_photo->loaded()) {
|
||||
connect(App::wnd(), SIGNAL(imageLoaded()), this, SLOT(update()));
|
||||
_photo->load();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int h = st::confirmInviteStatusTop + _status->height() + st::boxPadding.bottom() + st::boxButtonPadding.top() + _join->height() + st::boxButtonPadding.bottom();
|
||||
if (!_participants.isEmpty()) {
|
||||
int skip = (width() - 4 * st::confirmInviteUserPhotoSize) / 5;
|
||||
int padding = skip / 2;
|
||||
_userWidth = (st::confirmInviteUserPhotoSize + 2 * padding);
|
||||
int sumWidth = _participants.size() * _userWidth;
|
||||
int left = (width() - sumWidth) / 2;
|
||||
for_const (auto user, _participants) {
|
||||
auto name = new FlatLabel(this, st::confirmInviteUserName);
|
||||
name->resizeToWidth(st::confirmInviteUserPhotoSize + padding);
|
||||
name->setText(user->firstName.isEmpty() ? App::peerName(user) : user->firstName);
|
||||
name->moveToLeft(left + (padding / 2), st::confirmInviteUserNameTop);
|
||||
left += _userWidth;
|
||||
}
|
||||
|
||||
h += st::confirmInviteUserHeight;
|
||||
}
|
||||
setMaxHeight(h);
|
||||
|
||||
connect(_cancel, SIGNAL(clicked()), this, SLOT(onClose()));
|
||||
connect(_join, SIGNAL(clicked()), App::main(), SLOT(onInviteImport()));
|
||||
}
|
||||
|
||||
void ConfirmInviteBox::resizeEvent(QResizeEvent *e) {
|
||||
_title->move((width() - _title->width()) / 2, st::confirmInviteTitleTop);
|
||||
_status->move((width() - _status->width()) / 2, st::confirmInviteStatusTop);
|
||||
_join->moveToRight(st::boxButtonPadding.right(), height() - st::boxButtonPadding.bottom() - _join->height());
|
||||
_cancel->moveToRight(st::boxButtonPadding.right() + _join->width() + st::boxButtonPadding.left(), _join->y());
|
||||
}
|
||||
|
||||
void ConfirmInviteBox::paintEvent(QPaintEvent *e) {
|
||||
Painter p(this);
|
||||
if (paint(p)) return;
|
||||
|
||||
p.drawPixmap((width() - st::confirmInvitePhotoSize) / 2, st::confirmInvitePhotoTop, _photo->pixCircled(st::confirmInvitePhotoSize, st::confirmInvitePhotoSize));
|
||||
|
||||
int sumWidth = _participants.size() * _userWidth;
|
||||
int left = (width() - sumWidth) / 2;
|
||||
for_const (auto user, _participants) {
|
||||
user->paintUserpicLeft(p, st::confirmInviteUserPhotoSize, left + (_userWidth - st::confirmInviteUserPhotoSize) / 2, st::confirmInviteUserPhotoTop, width());
|
||||
left += _userWidth;
|
||||
}
|
||||
}
|
||||
|
||||
void ConfirmInviteBox::showAll() {
|
||||
showChildren();
|
||||
}
|
||||
|
||||
void ConfirmInviteBox::hideAll() {
|
||||
hideChildren();
|
||||
}
|
||||
|
|
|
@ -266,3 +266,26 @@ private:
|
|||
UserData *_member;
|
||||
|
||||
};
|
||||
|
||||
class ConfirmInviteBox : public AbstractBox, public RPCSender {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ConfirmInviteBox(const QString &title, const MTPChatPhoto &photo, int count, const QVector<UserData*> &participants);
|
||||
|
||||
protected:
|
||||
void resizeEvent(QResizeEvent *e) override;
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
|
||||
void showAll() override;
|
||||
void hideAll() override;
|
||||
|
||||
private:
|
||||
ChildWidget<FlatLabel> _title, _status;
|
||||
ImagePtr _photo;
|
||||
QVector<UserData*> _participants;
|
||||
|
||||
ChildWidget<BoxButton> _join, _cancel;
|
||||
int _userWidth = 0;
|
||||
|
||||
};
|
||||
|
|
306
Telegram/SourceFiles/boxes/confirmphonebox.cpp
Normal file
|
@ -0,0 +1,306 @@
|
|||
/*
|
||||
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 "boxes/confirmphonebox.h"
|
||||
|
||||
#include "styles/style_boxes.h"
|
||||
#include "boxes/confirmbox.h"
|
||||
#include "mainwidget.h"
|
||||
#include "lang.h"
|
||||
|
||||
namespace {
|
||||
|
||||
QPointer<ConfirmPhoneBox> CurrentConfirmPhoneBox = nullptr;
|
||||
|
||||
} // namespace
|
||||
|
||||
void ConfirmPhoneBox::start(const QString &phone, const QString &hash) {
|
||||
if (CurrentConfirmPhoneBox) {
|
||||
if (CurrentConfirmPhoneBox->getPhone() == phone) return;
|
||||
delete CurrentConfirmPhoneBox;
|
||||
}
|
||||
if (auto main = App::main()) {
|
||||
CurrentConfirmPhoneBox = new ConfirmPhoneBox(main, phone, hash);
|
||||
}
|
||||
}
|
||||
|
||||
ConfirmPhoneBox::ConfirmPhoneBox(QWidget *parent, const QString &phone, const QString &hash) : AbstractBox(st::boxWidth)
|
||||
, _phone(phone)
|
||||
, _hash(hash) {
|
||||
setParent(parent);
|
||||
|
||||
MTPaccount_SendConfirmPhoneCode::Flags flags = 0;
|
||||
_sendCodeRequestId = MTP::send(MTPaccount_SendConfirmPhoneCode(MTP_flags(flags), MTP_string(_hash), MTPBool()), rpcDone(&ConfirmPhoneBox::sendCodeDone), rpcFail(&ConfirmPhoneBox::sendCodeFail));
|
||||
}
|
||||
|
||||
void ConfirmPhoneBox::sendCodeDone(const MTPauth_SentCode &result) {
|
||||
_sendCodeRequestId = 0;
|
||||
|
||||
auto &resultInner = result.c_auth_sentCode();
|
||||
switch (resultInner.vtype.type()) {
|
||||
case mtpc_auth_sentCodeTypeApp: LOG(("Error: should not be in-app code!")); break;
|
||||
case mtpc_auth_sentCodeTypeSms: _sentCodeLength = resultInner.vtype.c_auth_sentCodeTypeSms().vlength.v; break;
|
||||
case mtpc_auth_sentCodeTypeCall: _sentCodeLength = resultInner.vtype.c_auth_sentCodeTypeCall().vlength.v; break;
|
||||
case mtpc_auth_sentCodeTypeFlashCall: LOG(("Error: should not be flashcall!")); break;
|
||||
}
|
||||
_phoneHash = qs(resultInner.vphone_code_hash);
|
||||
if (resultInner.has_next_type() && resultInner.vnext_type.type() == mtpc_auth_codeTypeCall) {
|
||||
setCallStatus({ CallState::Waiting, resultInner.has_timeout() ? resultInner.vtimeout.v : 60 });
|
||||
} else {
|
||||
setCallStatus({ CallState::Disabled, 0 });
|
||||
}
|
||||
launch();
|
||||
}
|
||||
|
||||
bool ConfirmPhoneBox::sendCodeFail(const RPCError &error) {
|
||||
auto errorText = lang(lng_server_error);
|
||||
if (MTP::isFloodError(error)) {
|
||||
errorText = lang(lng_flood_error);
|
||||
} else if (MTP::isDefaultHandledError(error)) {
|
||||
return false;
|
||||
} else if (error.code() == 400) {
|
||||
errorText = lang(lng_confirm_phone_link_invalid);
|
||||
}
|
||||
_sendCodeRequestId = 0;
|
||||
Ui::showLayer(new InformBox(errorText));
|
||||
deleteLater();
|
||||
return true;
|
||||
}
|
||||
|
||||
void ConfirmPhoneBox::setCallStatus(const CallStatus &status) {
|
||||
_callStatus = status;
|
||||
if (_callStatus.state == CallState::Waiting) {
|
||||
_callTimer.start(1000);
|
||||
}
|
||||
}
|
||||
|
||||
void ConfirmPhoneBox::launch() {
|
||||
setBlueTitle(true);
|
||||
|
||||
_about = new FlatLabel(this, st::confirmPhoneAboutLabel);
|
||||
TextWithEntities aboutText;
|
||||
auto formattedPhone = App::formatPhone(_phone);
|
||||
aboutText.text = lng_confirm_phone_about(lt_phone, formattedPhone);
|
||||
auto phonePosition = aboutText.text.indexOf(formattedPhone);
|
||||
if (phonePosition >= 0) {
|
||||
aboutText.entities.push_back(EntityInText(EntityInTextBold, phonePosition, formattedPhone.size()));
|
||||
}
|
||||
_about->setMarkedText(aboutText);
|
||||
|
||||
_code = new InputField(this, st::confirmPhoneCodeField, lang(lng_code_ph));
|
||||
|
||||
_send = new BoxButton(this, lang(lng_confirm_phone_send), st::defaultBoxButton);
|
||||
_cancel = new BoxButton(this, lang(lng_cancel), st::cancelBoxButton);
|
||||
|
||||
setMaxHeight(st::boxTitleHeight + st::usernamePadding.top() + _code->height() + st::usernameSkip + _about->height() + st::usernameSkip + _send->height() + st::boxButtonPadding.bottom());
|
||||
|
||||
connect(_send, SIGNAL(clicked()), this, SLOT(onSendCode()));
|
||||
connect(_cancel, SIGNAL(clicked()), this, SLOT(onClose()));
|
||||
|
||||
connect(_code, SIGNAL(changed()), this, SLOT(onCodeChanged()));
|
||||
connect(_code, SIGNAL(submitted(bool)), this, SLOT(onSendCode()));
|
||||
|
||||
connect(&_callTimer, SIGNAL(timeout()), this, SLOT(onCallStatusTimer()));
|
||||
|
||||
prepare();
|
||||
|
||||
Ui::showLayer(this);
|
||||
}
|
||||
|
||||
void ConfirmPhoneBox::onCallStatusTimer() {
|
||||
if (_callStatus.state == CallState::Waiting) {
|
||||
if (--_callStatus.timeout <= 0) {
|
||||
_callStatus.state = CallState::Calling;
|
||||
_callTimer.stop();
|
||||
MTP::send(MTPauth_ResendCode(MTP_string(_phone), MTP_string(_phoneHash)), rpcDone(&ConfirmPhoneBox::callDone));
|
||||
}
|
||||
}
|
||||
update();
|
||||
}
|
||||
|
||||
void ConfirmPhoneBox::callDone(const MTPauth_SentCode &result) {
|
||||
if (_callStatus.state == CallState::Calling) {
|
||||
_callStatus.state = CallState::Called;
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
void ConfirmPhoneBox::onSendCode() {
|
||||
if (_sendCodeRequestId) {
|
||||
return;
|
||||
}
|
||||
auto code = _code->getLastText();
|
||||
if (code.isEmpty()) {
|
||||
_code->showError();
|
||||
return;
|
||||
}
|
||||
|
||||
_code->setDisabled(true);
|
||||
setFocus();
|
||||
|
||||
showError(QString());
|
||||
|
||||
_sendCodeRequestId = MTP::send(MTPaccount_ConfirmPhone(MTP_string(_phoneHash), MTP_string(_code->getLastText())), rpcDone(&ConfirmPhoneBox::confirmDone), rpcFail(&ConfirmPhoneBox::confirmFail));
|
||||
}
|
||||
|
||||
void ConfirmPhoneBox::confirmDone(const MTPBool &result) {
|
||||
_sendCodeRequestId = 0;
|
||||
Ui::showLayer(new InformBox(lng_confirm_phone_success(lt_phone, App::formatPhone(_phone))));
|
||||
}
|
||||
|
||||
bool ConfirmPhoneBox::confirmFail(const RPCError &error) {
|
||||
auto errorText = lang(lng_server_error);
|
||||
if (MTP::isFloodError(error)) {
|
||||
errorText = lang(lng_flood_error);
|
||||
} else if (MTP::isDefaultHandledError(error)) {
|
||||
return false;
|
||||
} else {
|
||||
auto &errorType = error.type();
|
||||
if (errorType == qstr("PHONE_CODE_EMPTY") || errorType == qstr("PHONE_CODE_INVALID")) {
|
||||
errorText = lang(lng_bad_code);
|
||||
}
|
||||
}
|
||||
_sendCodeRequestId = 0;
|
||||
_code->setDisabled(false);
|
||||
_code->setFocus();
|
||||
showError(errorText);
|
||||
return true;
|
||||
}
|
||||
|
||||
void ConfirmPhoneBox::onCodeChanged() {
|
||||
if (_fixing) return;
|
||||
|
||||
_fixing = true;
|
||||
QString newText, now = _code->getLastText();
|
||||
int oldPos = _code->textCursor().position(), newPos = -1;
|
||||
int oldLen = now.size(), digitCount = 0;
|
||||
for_const (auto ch, now) {
|
||||
if (ch.isDigit()) {
|
||||
++digitCount;
|
||||
}
|
||||
}
|
||||
|
||||
if (_sentCodeLength > 0 && digitCount > _sentCodeLength) {
|
||||
digitCount = _sentCodeLength;
|
||||
}
|
||||
bool strict = (_sentCodeLength > 0 && digitCount == _sentCodeLength);
|
||||
|
||||
newText.reserve(oldLen);
|
||||
int i = 0;
|
||||
for_const (auto ch, now) {
|
||||
if (i++ == oldPos) {
|
||||
newPos = newText.length();
|
||||
}
|
||||
if (ch.isDigit()) {
|
||||
if (!digitCount--) {
|
||||
break;
|
||||
}
|
||||
newText += ch;
|
||||
if (strict && !digitCount) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (newPos < 0) {
|
||||
newPos = newText.length();
|
||||
}
|
||||
if (newText != now) {
|
||||
now = newText;
|
||||
_code->setText(now);
|
||||
_code->setCursorPosition(newPos);
|
||||
}
|
||||
_fixing = false;
|
||||
|
||||
showError(QString());
|
||||
if (strict) {
|
||||
onSendCode();
|
||||
}
|
||||
}
|
||||
|
||||
void ConfirmPhoneBox::showError(const QString &error) {
|
||||
_error = error;
|
||||
if (!_error.isEmpty()) {
|
||||
_code->showError();
|
||||
}
|
||||
update();
|
||||
}
|
||||
|
||||
void ConfirmPhoneBox::paintEvent(QPaintEvent *e) {
|
||||
Painter p(this);
|
||||
if (paint(p)) return;
|
||||
|
||||
paintTitle(p, lang(lng_confirm_phone_title));
|
||||
|
||||
p.setFont(st::boxTextFont);
|
||||
auto callText = getCallText();
|
||||
if (!callText.isEmpty()) {
|
||||
p.setPen(st::usernameDefaultFg);
|
||||
auto callTextRectLeft = st::usernamePadding.left();
|
||||
auto callTextRectTop = _about->y() + _about->height();
|
||||
auto callTextRectWidth = width() - 2 * st::usernamePadding.left();
|
||||
auto callTextRect = QRect(callTextRectLeft, callTextRectTop, callTextRectWidth, st::usernameSkip);
|
||||
p.drawText(callTextRect, callText, style::al_left);
|
||||
}
|
||||
auto errorText = _error;
|
||||
if (errorText.isEmpty()) {
|
||||
p.setPen(st::usernameDefaultFg);
|
||||
errorText = lang(lng_confirm_phone_enter_code);
|
||||
} else {
|
||||
p.setPen(st::setErrColor);
|
||||
}
|
||||
auto errorTextRectLeft = st::usernamePadding.left();
|
||||
auto errorTextRectTop = _code->y() + _code->height();
|
||||
auto errorTextRectWidth = width() - 2 * st::usernamePadding.left();
|
||||
auto errorTextRect = QRect(errorTextRectLeft, errorTextRectTop, errorTextRectWidth, st::usernameSkip);
|
||||
p.drawText(errorTextRect, errorText, style::al_left);
|
||||
}
|
||||
|
||||
QString ConfirmPhoneBox::getCallText() const {
|
||||
switch (_callStatus.state) {
|
||||
case CallState::Waiting: {
|
||||
if (_callStatus.timeout >= 3600) {
|
||||
return lng_code_call(lt_minutes, qsl("%1:%2").arg(_callStatus.timeout / 3600).arg((_callStatus.timeout / 60) % 60, 2, 10, QChar('0')), lt_seconds, qsl("%1").arg(_callStatus.timeout % 60, 2, 10, QChar('0')));
|
||||
}
|
||||
return lng_code_call(lt_minutes, QString::number(_callStatus.timeout / 60), lt_seconds, qsl("%1").arg(_callStatus.timeout % 60, 2, 10, QChar('0')));
|
||||
} break;
|
||||
case CallState::Calling: return lang(lng_code_calling);
|
||||
case CallState::Called: return lang(lng_code_called);
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
|
||||
void ConfirmPhoneBox::resizeEvent(QResizeEvent *e) {
|
||||
_code->resize(width() - st::usernamePadding.left() - st::usernamePadding.right(), _code->height());
|
||||
_code->moveToLeft(st::usernamePadding.left(), st::boxTitleHeight + st::usernamePadding.top());
|
||||
|
||||
_about->moveToLeft(st::usernamePadding.left(), _code->y() + _code->height() + st::usernameSkip);
|
||||
|
||||
_send->moveToRight(st::boxButtonPadding.right(), height() - st::boxButtonPadding.bottom() - _send->height());
|
||||
_cancel->moveToRight(st::boxButtonPadding.right() + _send->width() + st::boxButtonPadding.left(), _send->y());
|
||||
|
||||
AbstractBox::resizeEvent(e);
|
||||
}
|
||||
|
||||
ConfirmPhoneBox::~ConfirmPhoneBox() {
|
||||
if (_sendCodeRequestId) {
|
||||
MTP::cancel(_sendCodeRequestId);
|
||||
}
|
||||
}
|
107
Telegram/SourceFiles/boxes/confirmphonebox.h
Normal file
|
@ -0,0 +1,107 @@
|
|||
/*
|
||||
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 "boxes/abstractbox.h"
|
||||
|
||||
class FlatLabel;
|
||||
|
||||
class ConfirmPhoneBox : public AbstractBox, public RPCSender {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
static void start(const QString &phone, const QString &hash);
|
||||
|
||||
~ConfirmPhoneBox();
|
||||
|
||||
private slots:
|
||||
void onCallStatusTimer();
|
||||
void onSendCode();
|
||||
void onCodeChanged();
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
void resizeEvent(QResizeEvent *e) override;
|
||||
void hideAll() override {
|
||||
hideChildren();
|
||||
}
|
||||
void showAll() override {
|
||||
showChildren();
|
||||
}
|
||||
void showDone() override {
|
||||
_code->setFocus();
|
||||
}
|
||||
|
||||
private:
|
||||
ConfirmPhoneBox(QWidget *parent, const QString &phone, const QString &hash);
|
||||
void sendCodeDone(const MTPauth_SentCode &result);
|
||||
bool sendCodeFail(const RPCError &error);
|
||||
|
||||
void callDone(const MTPauth_SentCode &result);
|
||||
|
||||
void confirmDone(const MTPBool &result);
|
||||
bool confirmFail(const RPCError &error);
|
||||
|
||||
QString getPhone() const {
|
||||
return _phone;
|
||||
}
|
||||
void launch();
|
||||
|
||||
enum CallState {
|
||||
Waiting,
|
||||
Calling,
|
||||
Called,
|
||||
Disabled,
|
||||
};
|
||||
struct CallStatus {
|
||||
CallState state;
|
||||
int timeout;
|
||||
};
|
||||
void setCallStatus(const CallStatus &status);
|
||||
QString getCallText() const;
|
||||
|
||||
void showError(const QString &error);
|
||||
|
||||
mtpRequestId _sendCodeRequestId = 0;
|
||||
|
||||
// _hash from the link for account.sendConfirmPhoneCode call.
|
||||
// _phoneHash from auth.sentCode for account.confirmPhone call.
|
||||
QString _phone, _hash;
|
||||
QString _phoneHash;
|
||||
|
||||
// If we receive the code length, we autosubmit _code field when enough symbols is typed.
|
||||
int _sentCodeLength = 0;
|
||||
|
||||
mtpRequestId _checkCodeRequestId = 0;
|
||||
|
||||
ChildWidget<FlatLabel> _about = { nullptr };
|
||||
ChildWidget<BoxButton> _send = { nullptr };
|
||||
ChildWidget<BoxButton> _cancel = { nullptr };
|
||||
ChildWidget<InputField> _code = { nullptr };
|
||||
|
||||
// Flag for not calling onTextChanged() recursively.
|
||||
bool _fixing = false;
|
||||
QString _error;
|
||||
|
||||
CallStatus _callStatus;
|
||||
QTimer _callTimer;
|
||||
|
||||
};
|
|
@ -26,15 +26,10 @@ class PasscodeBox : public AbstractBox, public RPCSender {
|
|||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
PasscodeBox(bool turningOff = false);
|
||||
PasscodeBox(const QByteArray &newSalt, const QByteArray &curSalt, bool hasRecovery, const QString &hint, bool turningOff = false);
|
||||
void init();
|
||||
void paintEvent(QPaintEvent *e);
|
||||
void resizeEvent(QResizeEvent *e);
|
||||
|
||||
public slots:
|
||||
|
||||
private slots:
|
||||
void onSave(bool force = false);
|
||||
void onBadOldPasscode();
|
||||
void onOldChanged();
|
||||
|
@ -47,16 +42,17 @@ public slots:
|
|||
void onSubmit();
|
||||
|
||||
signals:
|
||||
|
||||
void reloadPassword();
|
||||
|
||||
protected:
|
||||
|
||||
void hideAll();
|
||||
void showAll();
|
||||
void showDone();
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
void resizeEvent(QResizeEvent *e) override;
|
||||
void hideAll() override;
|
||||
void showAll() override;
|
||||
void showDone() override;
|
||||
|
||||
private:
|
||||
void init();
|
||||
|
||||
void setPasswordDone(const MTPBool &result);
|
||||
bool setPasswordFail(const RPCError &error);
|
||||
|
|
|
@ -61,7 +61,7 @@ void PhotoCropBox::init(const QImage &img, PeerData *peer) {
|
|||
}
|
||||
|
||||
int32 s = st::boxWideWidth - st::boxPhotoPadding.left() - st::boxPhotoPadding.right();
|
||||
_thumb = QPixmap::fromImage(img.scaled(s, s, Qt::KeepAspectRatio, Qt::SmoothTransformation), Qt::ColorOnly);
|
||||
_thumb = App::pixmapFromImageInPlace(img.scaled(s, s, Qt::KeepAspectRatio, Qt::SmoothTransformation));
|
||||
_thumbw = _thumb.width();
|
||||
_thumbh = _thumb.height();
|
||||
if (_thumbw > _thumbh) {
|
||||
|
|
|
@ -110,7 +110,7 @@ PhotoSendBox::PhotoSendBox(const FileLoadResultPtr &file) : AbstractBox(st::boxW
|
|||
}
|
||||
_thumbx = (width() - _thumbw) / 2;
|
||||
|
||||
_thumb = QPixmap::fromImage(_thumb.toImage().scaled(_thumbw * cIntRetinaFactor(), _thumbh * cIntRetinaFactor(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation), Qt::ColorOnly);
|
||||
_thumb = App::pixmapFromImageInPlace(_thumb.toImage().scaled(_thumbw * cIntRetinaFactor(), _thumbh * cIntRetinaFactor(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
|
||||
_thumb.setDevicePixelRatio(cRetinaFactor());
|
||||
} else {
|
||||
if (_file->thumb.isNull()) {
|
||||
|
@ -483,7 +483,7 @@ EditCaptionBox::EditCaptionBox(HistoryItem *msg) : AbstractBox(st::boxWideWidth)
|
|||
}
|
||||
_thumbx = (width() - _thumbw) / 2;
|
||||
|
||||
_thumb = QPixmap::fromImage(_thumb.toImage().scaled(_thumbw * cIntRetinaFactor(), _thumbh * cIntRetinaFactor(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation), Qt::ColorOnly);
|
||||
_thumb = App::pixmapFromImageInPlace(_thumb.toImage().scaled(_thumbw * cIntRetinaFactor(), _thumbh * cIntRetinaFactor(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
|
||||
_thumb.setDevicePixelRatio(cRetinaFactor());
|
||||
}
|
||||
|
||||
|
|
|
@ -22,6 +22,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
|
||||
#include "abstractbox.h"
|
||||
|
||||
class ConfirmBox;
|
||||
|
||||
class StickerSetInner : public TWidget, public RPCSender {
|
||||
Q_OBJECT
|
||||
|
||||
|
@ -62,24 +64,26 @@ private:
|
|||
void gotSet(const MTPmessages_StickerSet &set);
|
||||
bool failedSet(const RPCError &error);
|
||||
|
||||
void installDone(const MTPBool &result);
|
||||
bool installFailed(const RPCError &error);
|
||||
void installDone(const MTPmessages_StickerSetInstallResult &result);
|
||||
bool installFail(const RPCError &error);
|
||||
|
||||
StickerPack _pack;
|
||||
StickersByEmojiMap _emoji;
|
||||
bool _loaded;
|
||||
uint64 _setId, _setAccess;
|
||||
bool _loaded = false;
|
||||
uint64 _setId = 0;
|
||||
uint64 _setAccess = 0;
|
||||
QString _title, _setTitle, _setShortName;
|
||||
int32 _setCount, _setHash;
|
||||
MTPDstickerSet::Flags _setFlags;
|
||||
int32 _setCount = 0;
|
||||
int32 _setHash = 0;
|
||||
MTPDstickerSet::Flags _setFlags = 0;
|
||||
|
||||
int32 _bottom;
|
||||
int32 _bottom = 0;
|
||||
MTPInputStickerSet _input;
|
||||
|
||||
mtpRequestId _installRequest;
|
||||
mtpRequestId _installRequest = 0;
|
||||
|
||||
QTimer _previewTimer;
|
||||
int32 _previewShown;
|
||||
int32 _previewShown = -1;
|
||||
};
|
||||
|
||||
class StickerSetBox : public ScrollableBox, public RPCSender {
|
||||
|
@ -93,7 +97,6 @@ public:
|
|||
void resizeEvent(QResizeEvent *e);
|
||||
|
||||
public slots:
|
||||
|
||||
void onStickersUpdated();
|
||||
void onAddStickers();
|
||||
void onShareStickers();
|
||||
|
@ -101,122 +104,49 @@ public slots:
|
|||
|
||||
void onScroll();
|
||||
|
||||
signals:
|
||||
private slots:
|
||||
void onInstalled(uint64 id);
|
||||
|
||||
signals:
|
||||
void installed(uint64 id);
|
||||
|
||||
protected:
|
||||
|
||||
void hideAll();
|
||||
void showAll();
|
||||
|
||||
private:
|
||||
|
||||
StickerSetInner _inner;
|
||||
ScrollableBoxShadow _shadow;
|
||||
BoxButton _add, _share, _cancel, _done;
|
||||
QString _title;
|
||||
|
||||
};
|
||||
|
||||
class StickersInner : public TWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
StickersInner();
|
||||
|
||||
void paintEvent(QPaintEvent *e);
|
||||
void mousePressEvent(QMouseEvent *e);
|
||||
void mouseMoveEvent(QMouseEvent *e);
|
||||
void mouseReleaseEvent(QMouseEvent *e);
|
||||
|
||||
void rebuild();
|
||||
bool savingStart() {
|
||||
if (_saving) return false;
|
||||
_saving = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
QVector<uint64> getOrder() const;
|
||||
QVector<uint64> getDisabledSets() const;
|
||||
|
||||
void setVisibleScrollbar(int32 width);
|
||||
|
||||
~StickersInner();
|
||||
|
||||
signals:
|
||||
|
||||
void checkDraggingScroll(int localY);
|
||||
void noDraggingScroll();
|
||||
|
||||
public slots:
|
||||
|
||||
void onUpdateSelected();
|
||||
|
||||
private:
|
||||
|
||||
void step_shifting(uint64 ms, bool timer);
|
||||
void paintRow(Painter &p, int32 index);
|
||||
void clear();
|
||||
void setRemoveSel(int32 removeSel);
|
||||
float64 aboveShadowOpacity() const;
|
||||
|
||||
int32 _rowHeight;
|
||||
struct StickerSetRow {
|
||||
StickerSetRow(uint64 id, DocumentData *sticker, int32 count, const QString &title, bool official, bool disabled, int32 pixw, int32 pixh) : id(id)
|
||||
, sticker(sticker)
|
||||
, count(count)
|
||||
, title(title)
|
||||
, official(official)
|
||||
, disabled(disabled)
|
||||
, pixw(pixw)
|
||||
, pixh(pixh)
|
||||
, yadd(0, 0) {
|
||||
}
|
||||
uint64 id;
|
||||
DocumentData *sticker;
|
||||
int32 count;
|
||||
QString title;
|
||||
bool official, disabled;
|
||||
int32 pixw, pixh;
|
||||
anim::ivalue yadd;
|
||||
};
|
||||
typedef QList<StickerSetRow*> StickerSetRows;
|
||||
StickerSetRows _rows;
|
||||
QList<uint64> _animStartTimes;
|
||||
uint64 _aboveShadowFadeStart;
|
||||
anim::fvalue _aboveShadowFadeOpacity;
|
||||
Animation _a_shifting;
|
||||
|
||||
int32 _itemsTop;
|
||||
|
||||
bool _saving;
|
||||
|
||||
int32 _removeSel, _removeDown, _removeWidth, _returnWidth, _restoreWidth;
|
||||
|
||||
QPoint _mouse;
|
||||
int32 _selected;
|
||||
QPoint _dragStart;
|
||||
int32 _started, _dragging, _above;
|
||||
|
||||
BoxShadow _aboveShadow;
|
||||
|
||||
int32 _scrollbar;
|
||||
};
|
||||
namespace internal {
|
||||
class StickersInner;
|
||||
} // namespace internal
|
||||
|
||||
class StickersBox : public ItemListBox, public RPCSender {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
enum class Section {
|
||||
Installed,
|
||||
Featured,
|
||||
Archived,
|
||||
ArchivedPart,
|
||||
};
|
||||
StickersBox(Section section = Section::Installed);
|
||||
StickersBox(const Stickers::Order &archivedIds);
|
||||
|
||||
StickersBox();
|
||||
void resizeEvent(QResizeEvent *e);
|
||||
void paintEvent(QPaintEvent *e);
|
||||
|
||||
void closePressed();
|
||||
|
||||
public slots:
|
||||
~StickersBox();
|
||||
|
||||
public slots:
|
||||
void onStickersUpdated();
|
||||
|
||||
void onCheckDraggingScroll(int localY);
|
||||
|
@ -225,35 +155,181 @@ public slots:
|
|||
|
||||
void onSave();
|
||||
|
||||
protected:
|
||||
private slots:
|
||||
void onScroll();
|
||||
|
||||
protected:
|
||||
void hideAll();
|
||||
void showAll();
|
||||
|
||||
private:
|
||||
|
||||
void setup();
|
||||
int32 countHeight() const;
|
||||
void rebuildList();
|
||||
|
||||
void disenableDone(const MTPBool &result, mtpRequestId req);
|
||||
void disenableDone(const MTPmessages_StickerSetInstallResult &result, mtpRequestId req);
|
||||
bool disenableFail(const RPCError &error, mtpRequestId req);
|
||||
void reorderDone(const MTPBool &result);
|
||||
bool reorderFail(const RPCError &result);
|
||||
void saveOrder();
|
||||
|
||||
StickersInner _inner;
|
||||
BoxButton _save, _cancel;
|
||||
void checkLoadMoreArchived();
|
||||
void getArchivedDone(uint64 offsetId, const MTPmessages_ArchivedStickers &result);
|
||||
|
||||
Section _section;
|
||||
|
||||
ChildWidget<internal::StickersInner> _inner;
|
||||
ChildWidget<BoxButton> _save = { nullptr };
|
||||
ChildWidget<BoxButton> _cancel = { nullptr };
|
||||
QMap<mtpRequestId, NullType> _disenableRequests;
|
||||
mtpRequestId _reorderRequest;
|
||||
PlainShadow _topShadow;
|
||||
ScrollableBoxShadow _bottomShadow;
|
||||
mtpRequestId _reorderRequest = 0;
|
||||
ChildWidget<PlainShadow> _topShadow = { nullptr };
|
||||
ChildWidget<ScrollableBoxShadow> _bottomShadow = { nullptr };
|
||||
|
||||
QTimer _scrollTimer;
|
||||
int32 _scrollDelta;
|
||||
int32 _scrollDelta = 0;
|
||||
|
||||
int32 _aboutWidth;
|
||||
int _aboutWidth = 0;
|
||||
Text _about;
|
||||
int32 _aboutHeight;
|
||||
int _aboutHeight = 0;
|
||||
|
||||
mtpRequestId _archivedRequestId = 0;
|
||||
bool _allArchivedLoaded = false;
|
||||
|
||||
};
|
||||
|
||||
int32 stickerPacksCount(bool includeDisabledOfficial = false);
|
||||
|
||||
namespace internal {
|
||||
|
||||
class StickersInner : public TWidget, public RPCSender {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
using Section = StickersBox::Section;
|
||||
StickersInner(Section section);
|
||||
StickersInner(const Stickers::Order &archivedIds);
|
||||
|
||||
void rebuild();
|
||||
void updateSize();
|
||||
void updateRows(); // refresh only pack cover stickers
|
||||
bool appendSet(const Stickers::Set &set);
|
||||
bool savingStart() {
|
||||
if (_saving) return false;
|
||||
_saving = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
Stickers::Order getOrder() const;
|
||||
Stickers::Order getDisabledSets() const;
|
||||
|
||||
void setVisibleScrollbar(int32 width);
|
||||
|
||||
~StickersInner();
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
void mousePressEvent(QMouseEvent *e) override;
|
||||
void mouseMoveEvent(QMouseEvent *e) override;
|
||||
void mouseReleaseEvent(QMouseEvent *e) override;
|
||||
void leaveEvent(QEvent *e) override;
|
||||
|
||||
signals:
|
||||
void checkDraggingScroll(int localY);
|
||||
void noDraggingScroll();
|
||||
|
||||
public slots:
|
||||
void onUpdateSelected();
|
||||
void onClearRecent();
|
||||
void onClearBoxDestroyed(QObject *box);
|
||||
|
||||
private:
|
||||
void setup();
|
||||
void paintButton(Painter &p, int y, bool selected, const QString &text, int badgeCounter) const;
|
||||
|
||||
void step_shifting(uint64 ms, bool timer);
|
||||
void paintRow(Painter &p, int32 index);
|
||||
void clear();
|
||||
void setActionSel(int32 actionSel);
|
||||
float64 aboveShadowOpacity() const;
|
||||
|
||||
void installSet(uint64 setId);
|
||||
void installDone(const MTPmessages_StickerSetInstallResult &result);
|
||||
bool installFail(uint64 setId, const RPCError &error);
|
||||
void readFeaturedDone(const MTPBool &result);
|
||||
bool readFeaturedFail(const RPCError &error);
|
||||
|
||||
Section _section;
|
||||
Stickers::Order _archivedIds;
|
||||
|
||||
int32 _rowHeight;
|
||||
struct StickerSetRow {
|
||||
StickerSetRow(uint64 id, DocumentData *sticker, int32 count, const QString &title, bool installed, bool official, bool unread, bool disabled, bool recent, int32 pixw, int32 pixh) : id(id)
|
||||
, sticker(sticker)
|
||||
, count(count)
|
||||
, title(title)
|
||||
, installed(installed)
|
||||
, official(official)
|
||||
, unread(unread)
|
||||
, disabled(disabled)
|
||||
, recent(recent)
|
||||
, pixw(pixw)
|
||||
, pixh(pixh)
|
||||
, yadd(0, 0) {
|
||||
}
|
||||
uint64 id;
|
||||
DocumentData *sticker;
|
||||
int32 count;
|
||||
QString title;
|
||||
bool installed, official, unread, disabled, recent;
|
||||
int32 pixw, pixh;
|
||||
anim::ivalue yadd;
|
||||
};
|
||||
using StickerSetRows = QList<StickerSetRow*>;
|
||||
|
||||
void rebuildAppendSet(const Stickers::Set &set, int maxNameWidth);
|
||||
void fillSetCover(const Stickers::Set &set, DocumentData **outSticker, int *outWidth, int *outHeight) const;
|
||||
int fillSetCount(const Stickers::Set &set) const;
|
||||
QString fillSetTitle(const Stickers::Set &set, int maxNameWidth) const;
|
||||
void fillSetFlags(const Stickers::Set &set, bool *outRecent, bool *outInstalled, bool *outOfficial, bool *outUnread, bool *outDisabled);
|
||||
|
||||
int countMaxNameWidth() const;
|
||||
|
||||
StickerSetRows _rows;
|
||||
QList<uint64> _animStartTimes;
|
||||
uint64 _aboveShadowFadeStart = 0;
|
||||
anim::fvalue _aboveShadowFadeOpacity = { 0., 0. };
|
||||
Animation _a_shifting;
|
||||
|
||||
int32 _itemsTop;
|
||||
|
||||
bool _saving = false;
|
||||
|
||||
int _actionSel = -1;
|
||||
int _actionDown = -1;
|
||||
|
||||
int _clearWidth, _removeWidth, _returnWidth, _restoreWidth;
|
||||
|
||||
ConfirmBox *_clearBox = nullptr;
|
||||
|
||||
int _buttonHeight = 0;
|
||||
bool _hasFeaturedButton = false;
|
||||
bool _hasArchivedButton = false;
|
||||
|
||||
// Remember all the unread set ids to display unread dots.
|
||||
OrderedSet<uint64> _unreadSets;
|
||||
|
||||
QPoint _mouse;
|
||||
int _selected = -3; // -2 - featured stickers button, -1 - archived stickers button
|
||||
int _pressed = -2;
|
||||
QPoint _dragStart;
|
||||
int _started = -1;
|
||||
int _dragging = -1;
|
||||
int _above = -1;
|
||||
|
||||
BoxShadow _aboveShadow;
|
||||
|
||||
int32 _scrollbar = 0;
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
|
|
|
@ -83,21 +83,18 @@ void UsernameBox::paintEvent(QPaintEvent *e) {
|
|||
|
||||
paintTitle(p, lang(lng_username_title));
|
||||
|
||||
p.setFont(st::boxTextFont);
|
||||
if (!_copiedTextLink.isEmpty()) {
|
||||
p.setPen(st::usernameDefaultFg);
|
||||
p.setFont(st::boxTextFont);
|
||||
p.drawTextLeft(st::usernamePadding.left(), _username.y() + _username.height() + ((st::usernameSkip - st::boxTextFont->height) / 2), width(), _copiedTextLink);
|
||||
} else if (!_errorText.isEmpty()) {
|
||||
p.setPen(st::setErrColor);
|
||||
p.setFont(st::boxTextFont);
|
||||
p.drawTextLeft(st::usernamePadding.left(), _username.y() + _username.height() + ((st::usernameSkip - st::boxTextFont->height) / 2), width(), _errorText);
|
||||
} else if (!_goodText.isEmpty()) {
|
||||
p.setPen(st::setGoodColor);
|
||||
p.setFont(st::boxTextFont);
|
||||
p.drawTextLeft(st::usernamePadding.left(), _username.y() + _username.height() + ((st::usernameSkip - st::boxTextFont->height) / 2), width(), _goodText);
|
||||
} else {
|
||||
p.setPen(st::usernameDefaultFg);
|
||||
p.setFont(st::boxTextFont);
|
||||
p.drawTextLeft(st::usernamePadding.left(), _username.y() + _username.height() + ((st::usernameSkip - st::boxTextFont->height) / 2), width(), lang(lng_username_choose));
|
||||
}
|
||||
p.setPen(st::black);
|
||||
|
|
|
@ -327,8 +327,11 @@ bool Generator::writeHeaderStyleNamespace() {
|
|||
header_->stream() << "void init_" << baseName_ << "();\n\n";
|
||||
header_->popNamespace();
|
||||
}
|
||||
bool wroteForwardDeclarations = writeStructsForwardDeclarations();
|
||||
if (module_.hasStructs()) {
|
||||
header_->newline();
|
||||
if (!wroteForwardDeclarations) {
|
||||
header_->newline();
|
||||
}
|
||||
if (!writeStructsDefinitions()) {
|
||||
return false;
|
||||
}
|
||||
|
@ -338,6 +341,32 @@ bool Generator::writeHeaderStyleNamespace() {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool Generator::writeStructsForwardDeclarations() {
|
||||
bool hasNoExternalStructs = module_.enumVariables([this](const Variable &value) -> bool {
|
||||
if (value.value.type().tag == structure::TypeTag::Struct) {
|
||||
if (!module_.findStructInModule(value.value.type().name, module_)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
if (hasNoExternalStructs) {
|
||||
return false;
|
||||
}
|
||||
|
||||
header_->newline();
|
||||
bool result = module_.enumVariables([this](const Variable &value) -> bool {
|
||||
if (value.value.type().tag == structure::TypeTag::Struct) {
|
||||
if (!module_.findStructInModule(value.value.type().name, module_)) {
|
||||
header_->stream() << "struct " << value.value.type().name.back() << ";\n";
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
header_->newline();
|
||||
return result;
|
||||
}
|
||||
|
||||
bool Generator::writeStructsDefinitions() {
|
||||
if (!module_.hasStructs()) {
|
||||
return true;
|
||||
|
|
|
@ -47,6 +47,7 @@ private:
|
|||
QString valueAssignmentCode(structure::Value value) const;
|
||||
|
||||
bool writeHeaderStyleNamespace();
|
||||
bool writeStructsForwardDeclarations();
|
||||
bool writeStructsDefinitions();
|
||||
bool writeRefsDeclarations();
|
||||
|
||||
|
|
|
@ -94,6 +94,9 @@ public:
|
|||
return !fullpath_.isEmpty();
|
||||
}
|
||||
|
||||
const Struct *findStructInModule(const FullName &name, const Module &module) const;
|
||||
const Variable *findVariableInModule(const FullName &name, const Module &module) const;
|
||||
|
||||
private:
|
||||
QString fullpath_;
|
||||
std::vector<std::unique_ptr<Module>> included_;
|
||||
|
@ -102,9 +105,6 @@ private:
|
|||
QMap<QString, int> structsByName_;
|
||||
QMap<QString, int> variablesByName_;
|
||||
|
||||
const Struct *findStructInModule(const FullName &name, const Module &module) const;
|
||||
const Variable *findVariableInModule(const FullName &name, const Module &module) const;
|
||||
|
||||
};
|
||||
|
||||
} // namespace structure
|
||||
|
|
|
@ -100,20 +100,19 @@ enum {
|
|||
// a new message from the same sender is attached to previous within 15 minutes
|
||||
AttachMessageToPreviousSecondsDelta = 900,
|
||||
|
||||
AudioVoiceMsgSimultaneously = 4,
|
||||
AudioSongSimultaneously = 4,
|
||||
AudioSimultaneousLimit = 4,
|
||||
AudioCheckPositionTimeout = 100, // 100ms per check audio pos
|
||||
AudioCheckPositionDelta = 2400, // update position called each 2400 samples
|
||||
AudioFadeTimeout = 7, // 7ms
|
||||
AudioFadeDuration = 500,
|
||||
AudioVoiceMsgSkip = 400, // 200ms
|
||||
AudioVoiceMsgFade = 300, // 300ms
|
||||
AudioPreloadSamples = 5 * 48000, // preload next part if less than 5 seconds remains
|
||||
AudioPreloadSamples = 2 * 48000, // preload next part if less than 5 seconds remains
|
||||
AudioVoiceMsgFrequency = 48000, // 48 kHz
|
||||
AudioVoiceMsgMaxLength = 100 * 60, // 100 minutes
|
||||
AudioVoiceMsgUpdateView = 100, // 100ms
|
||||
AudioVoiceMsgChannels = 2, // stereo
|
||||
AudioVoiceMsgBufferSize = 1024 * 1024, // 1 Mb buffers
|
||||
AudioVoiceMsgBufferSize = 256 * 1024, // 256 Kb buffers (1.3 - 3.0 secs)
|
||||
AudioVoiceMsgInMemory = 2 * 1024 * 1024, // 2 Mb audio is hold in memory and auto loaded
|
||||
AudioPauseDeviceTimeout = 3000, // pause in 3 secs after playing is over
|
||||
|
||||
|
|
|
@ -24,6 +24,11 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
|
||||
#include <openssl/crypto.h>
|
||||
#include <openssl/sha.h>
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/engine.h>
|
||||
#include <openssl/conf.h>
|
||||
#include <openssl/ssl.h>
|
||||
|
||||
extern "C" {
|
||||
#include <libavcodec/avcodec.h>
|
||||
|
@ -288,10 +293,19 @@ namespace ThirdParty {
|
|||
}
|
||||
|
||||
void finish() {
|
||||
av_lockmgr_register(0);
|
||||
av_lockmgr_register(nullptr);
|
||||
|
||||
CRYPTO_cleanup_all_ex_data();
|
||||
FIPS_mode_set(0);
|
||||
ENGINE_cleanup();
|
||||
CONF_modules_unload(1);
|
||||
ERR_remove_state(0);
|
||||
ERR_free_strings();
|
||||
ERR_remove_thread_state(nullptr);
|
||||
EVP_cleanup();
|
||||
|
||||
delete[] _sslLocks;
|
||||
_sslLocks = 0;
|
||||
_sslLocks = nullptr;
|
||||
|
||||
Platform::ThirdParty::finish();
|
||||
}
|
||||
|
|
|
@ -35,7 +35,7 @@ T *getPointerAndReset(T *&ptr) {
|
|||
|
||||
template <typename T>
|
||||
T createAndSwap(T &value) {
|
||||
T result;
|
||||
T result = T();
|
||||
std::swap(result, value);
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -24,6 +24,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
#include "lang.h"
|
||||
#include "pspecific.h"
|
||||
#include "boxes/confirmbox.h"
|
||||
#include "core/qthelp_regex.h"
|
||||
#include "core/qthelp_url.h"
|
||||
|
||||
QString UrlClickHandler::copyToClipboardContextItemText() const {
|
||||
return lang(isEmail() ? lng_context_copy_email : lng_context_copy_link);
|
||||
|
@ -31,23 +33,28 @@ QString UrlClickHandler::copyToClipboardContextItemText() const {
|
|||
|
||||
namespace {
|
||||
|
||||
QString tryConvertUrlToLocal(const QString &url) {
|
||||
QRegularExpressionMatch telegramMeUser = QRegularExpression(qsl("^https?://telegram\\.me/([a-zA-Z0-9\\.\\_]+)(/?\\?|/?$|/(\\d+)/?(?:\\?|$))"), QRegularExpression::CaseInsensitiveOption).match(url);
|
||||
QRegularExpressionMatch telegramMeGroup = QRegularExpression(qsl("^https?://telegram\\.me/joinchat/([a-zA-Z0-9\\.\\_\\-]+)(\\?|$)"), QRegularExpression::CaseInsensitiveOption).match(url);
|
||||
QRegularExpressionMatch telegramMeStickers = QRegularExpression(qsl("^https?://telegram\\.me/addstickers/([a-zA-Z0-9\\.\\_]+)(\\?|$)"), QRegularExpression::CaseInsensitiveOption).match(url);
|
||||
QRegularExpressionMatch telegramMeShareUrl = QRegularExpression(qsl("^https?://telegram\\.me/share/url\\?(.+)$"), QRegularExpression::CaseInsensitiveOption).match(url);
|
||||
if (telegramMeGroup.hasMatch()) {
|
||||
return qsl("tg://join?invite=") + myUrlEncode(telegramMeGroup.captured(1));
|
||||
} else if (telegramMeStickers.hasMatch()) {
|
||||
return qsl("tg://addstickers?set=") + myUrlEncode(telegramMeStickers.captured(1));
|
||||
} else if (telegramMeShareUrl.hasMatch()) {
|
||||
return qsl("tg://msg_url?") + telegramMeShareUrl.captured(1);
|
||||
} else if (telegramMeUser.hasMatch()) {
|
||||
QString params = url.mid(telegramMeUser.captured(0).size()), postParam;
|
||||
if (QRegularExpression(qsl("^/\\d+/?(?:\\?|$)")).match(telegramMeUser.captured(2)).hasMatch()) {
|
||||
postParam = qsl("&post=") + telegramMeUser.captured(3);
|
||||
QString tryConvertUrlToLocal(QString url) {
|
||||
if (url.size() > 8192) url = url.mid(0, 8192);
|
||||
|
||||
using namespace qthelp;
|
||||
auto matchOptions = RegExOption::CaseInsensitive;
|
||||
if (auto telegramMeMatch = regex_match(qsl("https?://telegram\\.me/(.+)$"), url, matchOptions)) {
|
||||
auto query = telegramMeMatch->capturedRef(1);
|
||||
if (auto joinChatMatch = regex_match(qsl("^joinchat/([a-zA-Z0-9\\.\\_\\-]+)(\\?|$)"), query, matchOptions)) {
|
||||
return qsl("tg://join?invite=") + url_encode(joinChatMatch->captured(1));
|
||||
} else if (auto stickerSetMatch = regex_match(qsl("^addstickers/([a-zA-Z0-9\\.\\_]+)(\\?|$)"), query, matchOptions)) {
|
||||
return qsl("tg://addstickers?set=") + url_encode(stickerSetMatch->captured(1));
|
||||
} else if (auto shareUrlMatch = regex_match(qsl("^share/url/?\\?(.+)$"), query, matchOptions)) {
|
||||
return qsl("tg://msg_url?") + shareUrlMatch->captured(1);
|
||||
} else if (auto confirmPhoneMatch = regex_match(qsl("^confirmphone/?\\?(.+)"), query, matchOptions)) {
|
||||
return qsl("tg://confirmphone?") + confirmPhoneMatch->captured(1);
|
||||
} else if (auto usernameMatch = regex_match(qsl("^([a-zA-Z0-9\\.\\_]+)(/?\\?|/?$|/(\\d+)/?(?:\\?|$))"), query, matchOptions)) {
|
||||
QString params = url.mid(usernameMatch->captured(0).size()), postParam;
|
||||
if (auto postMatch = regex_match(qsl("^/\\d+/?(?:\\?|$)"), usernameMatch->captured(2))) {
|
||||
postParam = qsl("&post=") + usernameMatch->captured(3);
|
||||
}
|
||||
return qsl("tg://resolve/?domain=") + url_encode(usernameMatch->captured(1)) + postParam + (params.isEmpty() ? QString() : '&' + params);
|
||||
}
|
||||
return qsl("tg://resolve/?domain=") + myUrlEncode(telegramMeUser.captured(1)) + postParam + (params.isEmpty() ? QString() : '&' + params);
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
|
71
Telegram/SourceFiles/core/qthelp_regex.h
Normal file
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
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
|
||||
|
||||
namespace qthelp {
|
||||
|
||||
class RegularExpressionMatch {
|
||||
public:
|
||||
RegularExpressionMatch(QRegularExpressionMatch &&match) : data_(std_::move(match)) {
|
||||
}
|
||||
RegularExpressionMatch(RegularExpressionMatch &&other) : data_(std_::move(other.data_)) {
|
||||
}
|
||||
QRegularExpressionMatch *operator->() {
|
||||
return &data_;
|
||||
}
|
||||
const QRegularExpressionMatch *operator->() const {
|
||||
return &data_;
|
||||
}
|
||||
explicit operator bool() const {
|
||||
return data_.hasMatch();
|
||||
}
|
||||
|
||||
private:
|
||||
QRegularExpressionMatch data_;
|
||||
|
||||
};
|
||||
|
||||
enum class RegExOption {
|
||||
None = QRegularExpression::NoPatternOption,
|
||||
CaseInsensitive = QRegularExpression::CaseInsensitiveOption,
|
||||
DotMatchesEverything = QRegularExpression::DotMatchesEverythingOption,
|
||||
Multiline = QRegularExpression::MultilineOption,
|
||||
ExtendedSyntax = QRegularExpression::ExtendedPatternSyntaxOption,
|
||||
InvertedGreediness = QRegularExpression::InvertedGreedinessOption,
|
||||
DontCapture = QRegularExpression::DontCaptureOption,
|
||||
UseUnicodeProperties = QRegularExpression::UseUnicodePropertiesOption,
|
||||
OptimizeOnFirstUsage = QRegularExpression::OptimizeOnFirstUsageOption,
|
||||
DontAutomaticallyOptimize = QRegularExpression::DontAutomaticallyOptimizeOption,
|
||||
};
|
||||
Q_DECLARE_FLAGS(RegExOptions, RegExOption);
|
||||
Q_DECLARE_OPERATORS_FOR_FLAGS(RegExOptions);
|
||||
|
||||
inline RegularExpressionMatch regex_match(const QString &string, const QString &subject, RegExOptions options = 0) {
|
||||
auto qtOptions = QRegularExpression::PatternOptions(static_cast<int>(options));
|
||||
return RegularExpressionMatch(QRegularExpression(string, qtOptions).match(subject));
|
||||
}
|
||||
|
||||
inline RegularExpressionMatch regex_match(const QString &string, const QStringRef &subjectRef, RegExOptions options = 0) {
|
||||
auto qtOptions = QRegularExpression::PatternOptions(static_cast<int>(options));
|
||||
return RegularExpressionMatch(QRegularExpression(string, qtOptions).match(subjectRef));
|
||||
}
|
||||
|
||||
} // namespace qthelp
|
52
Telegram/SourceFiles/core/qthelp_url.cpp
Normal file
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
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 "core/qthelp_url.h"
|
||||
|
||||
namespace qthelp {
|
||||
|
||||
QMap<QString, QString> url_parse_params(const QString ¶ms, UrlParamNameTransform transform) {
|
||||
QMap<QString, QString> result;
|
||||
|
||||
auto transformParamName = [transform](const QString &name) {
|
||||
if (transform == UrlParamNameTransform::ToLower) {
|
||||
return name.toLower();
|
||||
}
|
||||
return name;
|
||||
};
|
||||
|
||||
auto paramsList = params.split('&');
|
||||
for_const (auto ¶m, paramsList) {
|
||||
// Skip params without a name (starting with '=').
|
||||
if (auto separatorPosition = param.indexOf('=')) {
|
||||
auto paramName = param;
|
||||
auto paramValue = QString();
|
||||
if (separatorPosition > 0) {
|
||||
paramName = param.mid(0, separatorPosition);
|
||||
paramValue = url_decode(param.mid(separatorPosition + 1));
|
||||
}
|
||||
result.insert(transformParamName(paramName), paramValue);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace qthelp
|
40
Telegram/SourceFiles/core/qthelp_url.h
Normal file
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
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
|
||||
|
||||
namespace qthelp {
|
||||
|
||||
inline QString url_encode(const QString &part) {
|
||||
return QString::fromLatin1(QUrl::toPercentEncoding(part));
|
||||
}
|
||||
|
||||
inline QString url_decode(const QString &encoded) {
|
||||
return QUrl::fromPercentEncoding(encoded.toUtf8());
|
||||
}
|
||||
|
||||
enum class UrlParamNameTransform {
|
||||
NoTransform,
|
||||
ToLower,
|
||||
};
|
||||
// Parses a string like "p1=v1&p2=v2&..&pn=vn" to a map.
|
||||
QMap<QString, QString> url_parse_params(const QString ¶ms, UrlParamNameTransform transform = UrlParamNameTransform::NoTransform);
|
||||
|
||||
} // namespace qthelp
|
|
@ -141,7 +141,10 @@ private:
|
|||
void reallocate(int newCapacity) {
|
||||
auto newPlainData = operator new[](newCapacity * sizeof(T));
|
||||
for (int i = 0; i < _size; ++i) {
|
||||
*(reinterpret_cast<T*>(newPlainData) + i) = std_::move(*(data() + i));
|
||||
auto oldLocation = data() + i;
|
||||
auto newLocation = reinterpret_cast<T*>(newPlainData) + i;
|
||||
new (newLocation) T(std_::move(*oldLocation));
|
||||
oldLocation->~T();
|
||||
}
|
||||
std::swap(_plaindata, newPlainData);
|
||||
_capacity = newCapacity;
|
||||
|
|
|
@ -24,7 +24,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
|
||||
#define BETA_VERSION_MACRO (0ULL)
|
||||
|
||||
constexpr int AppVersion = 9057;
|
||||
constexpr str_const AppVersionStr = "0.9.57";
|
||||
constexpr bool AppAlphaVersion = true;
|
||||
constexpr int AppVersion = 10000;
|
||||
constexpr str_const AppVersionStr = "0.10";
|
||||
constexpr bool AppAlphaVersion = false;
|
||||
constexpr uint64 AppBetaVersion = BETA_VERSION_MACRO;
|
||||
|
|
|
@ -29,7 +29,6 @@ dialogsUnreadBgActive: #ffffff;
|
|||
dialogsUnreadBgMutedActive: #d3e2ee;
|
||||
dialogsUnreadFont: font(12px bold);
|
||||
dialogsUnreadHeight: 19px;
|
||||
dialogsUnreadTop: 1px;
|
||||
dialogsUnreadPadding: 5px;
|
||||
|
||||
dialogsBg: windowBg;
|
||||
|
@ -86,8 +85,8 @@ dialogsTextStyleActive: textStyle(dialogsTextStyle) {
|
|||
linkFgDown: dialogsTextFgActive;
|
||||
}
|
||||
dialogsTextStyleDraftActive: textStyle(dialogsTextStyle) {
|
||||
linkFg: #ffd6d6;
|
||||
linkFgDown: #ffd6d6;
|
||||
linkFg: #c6e1f7;
|
||||
linkFgDown: #c6e1f7;
|
||||
}
|
||||
|
||||
dialogsNewChatIcon: icon {
|
||||
|
|
|
@ -33,11 +33,16 @@ namespace Layout {
|
|||
|
||||
namespace {
|
||||
|
||||
// Show all dates that are in the last 20 hours in time format.
|
||||
constexpr int kRecentlyInSeconds = 20 * 3600;
|
||||
|
||||
void paintRowDate(Painter &p, const QDateTime &date, QRect &rectForName, bool active) {
|
||||
QDateTime now(QDateTime::currentDateTime()), lastTime(date);
|
||||
QDate nowDate(now.date()), lastDate(lastTime.date());
|
||||
QString dt;
|
||||
if (lastDate == nowDate) {
|
||||
bool wasSameDay = (lastDate == nowDate);
|
||||
bool wasRecently = qAbs(lastTime.secsTo(now)) < kRecentlyInSeconds;
|
||||
if (wasSameDay || wasRecently) {
|
||||
dt = lastTime.toString(cTimeFormat());
|
||||
} else if (lastDate.year() == nowDate.year() && lastDate.weekNumber() == nowDate.weekNumber()) {
|
||||
dt = langDayOfWeek(lastDate);
|
||||
|
@ -222,7 +227,7 @@ void paintUnreadCount(Painter &p, const QString &text, int x, int y, const Unrea
|
|||
|
||||
p.setFont(st.font);
|
||||
p.setPen(st.active ? st::dialogsUnreadFgActive : st::dialogsUnreadFg);
|
||||
p.drawText(unreadRectLeft + (unreadRectWidth - unreadWidth) / 2, unreadRectTop + st::dialogsUnreadTop + st.font->ascent, text);
|
||||
p.drawText(unreadRectLeft + (unreadRectWidth - unreadWidth) / 2, unreadRectTop + (unreadRectHeight - st.font->height) / 2 + st.font->ascent, text);
|
||||
}
|
||||
|
||||
void RowPainter::paint(Painter &p, const Row *row, int w, bool active, bool selected, bool onlyBackground) {
|
||||
|
@ -258,7 +263,7 @@ void RowPainter::paint(Painter &p, const Row *row, int w, bool active, bool sele
|
|||
auto counter = QString::number(unreadCount);
|
||||
auto mutedCounter = history->mute();
|
||||
int unreadRight = w - st::dialogsPadding.x();
|
||||
int unreadTop = texttop + st::dialogsTextFont->ascent - st::dialogsUnreadFont->ascent - st::dialogsUnreadTop;
|
||||
int unreadTop = texttop + st::dialogsTextFont->ascent - st::dialogsUnreadFont->ascent - (st::dialogsUnreadHeight - st::dialogsUnreadFont->height) / 2;
|
||||
int unreadWidth = 0;
|
||||
|
||||
UnreadBadgeStyle st;
|
||||
|
@ -297,7 +302,7 @@ void paintImportantSwitch(Painter &p, Mode current, int w, bool selected, bool o
|
|||
int unreadTop = (st::dialogsImportantBarHeight - st::dialogsUnreadHeight) / 2;
|
||||
bool mutedHidden = (current == Dialogs::Mode::Important);
|
||||
QString text = mutedHidden ? qsl("Show all chats") : qsl("Hide muted chats");
|
||||
int textBaseline = unreadTop + st::dialogsUnreadTop + st::dialogsUnreadFont->ascent;
|
||||
int textBaseline = unreadTop + (st::dialogsUnreadHeight - st::dialogsUnreadFont->height) / 2 + st::dialogsUnreadFont->ascent;
|
||||
p.drawText(st::dialogsPadding.x(), textBaseline, text);
|
||||
|
||||
if (mutedHidden) {
|
||||
|
|
|
@ -38,6 +38,8 @@ void paintImportantSwitch(Painter &p, Mode current, int w, bool selected, bool o
|
|||
enum UnreadBadgeSize {
|
||||
UnreadBadgeInDialogs = 0,
|
||||
UnreadBadgeInHistoryToDown,
|
||||
UnreadBadgeInStickersPanel,
|
||||
UnreadBadgeInStickersBox,
|
||||
|
||||
UnreadBadgeSizesCount
|
||||
};
|
||||
|
|
|
@ -2214,6 +2214,7 @@ void DialogsWidget::searchReceived(DialogsSearchRequestType type, const MTPmessa
|
|||
|
||||
_searchRequest = 0;
|
||||
onListScroll();
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
#include "boxes/stickersetbox.h"
|
||||
#include "inline_bots/inline_bot_result.h"
|
||||
#include "inline_bots/inline_bot_layout_item.h"
|
||||
#include "dialogs/dialogs_layout.h"
|
||||
#include "historywidget.h"
|
||||
#include "localstorage.h"
|
||||
#include "lang.h"
|
||||
|
@ -713,7 +714,7 @@ void EmojiColorPicker::drawVariant(Painter &p, int variant) {
|
|||
}
|
||||
|
||||
EmojiPanInner::EmojiPanInner() : TWidget()
|
||||
, _maxHeight(int(st::emojiPanMaxHeight))
|
||||
, _maxHeight(int(st::emojiPanMaxHeight) - st::rbEmoji.height)
|
||||
, _a_selected(animation(this, &EmojiPanInner::step_selected))
|
||||
, _top(0)
|
||||
, _selected(-1)
|
||||
|
@ -1229,7 +1230,7 @@ StickerPanInner::StickerPanInner() : TWidget()
|
|||
, _pressedSel(-1)
|
||||
, _settings(this, lang(lng_stickers_you_have))
|
||||
, _previewShown(false) {
|
||||
setMaxHeight(st::emojiPanMaxHeight);
|
||||
setMaxHeight(st::emojiPanMaxHeight - st::rbEmoji.height);
|
||||
|
||||
setMouseTracking(true);
|
||||
setFocusPolicy(Qt::NoFocus);
|
||||
|
@ -1508,7 +1509,7 @@ void StickerPanInner::mouseReleaseEvent(QMouseEvent *e) {
|
|||
break;
|
||||
}
|
||||
}
|
||||
Stickers::Sets &sets(Global::RefStickerSets());
|
||||
auto &sets = Global::RefStickerSets();
|
||||
auto it = sets.find(Stickers::CustomSetId);
|
||||
if (it != sets.cend()) {
|
||||
for (int32 i = 0, l = it->stickers.size(); i < l; ++i) {
|
||||
|
@ -1517,7 +1518,7 @@ void StickerPanInner::mouseReleaseEvent(QMouseEvent *e) {
|
|||
if (it->stickers.isEmpty()) {
|
||||
sets.erase(it);
|
||||
}
|
||||
Local::writeStickers();
|
||||
Local::writeInstalledStickers();
|
||||
refresh = true;
|
||||
break;
|
||||
}
|
||||
|
@ -1628,8 +1629,8 @@ void StickerPanInner::hideFinish(bool completely) {
|
|||
void StickerPanInner::refreshStickers() {
|
||||
clearSelection(true);
|
||||
|
||||
const Stickers::Sets &sets(Global::StickerSets());
|
||||
_sets.clear(); _sets.reserve(sets.size() + 1);
|
||||
_sets.clear();
|
||||
_sets.reserve(Global::StickerSetsOrder().size() + 1);
|
||||
|
||||
refreshRecentStickers(false);
|
||||
for (auto i = Global::StickerSetsOrder().cbegin(), e = Global::StickerSetsOrder().cend(); i != e; ++i) {
|
||||
|
@ -2085,9 +2086,9 @@ bool StickerPanInner::ui_isInlineItemBeingChosen() {
|
|||
}
|
||||
|
||||
void StickerPanInner::appendSet(uint64 setId) {
|
||||
const Stickers::Sets &sets(Global::StickerSets());
|
||||
auto &sets = Global::StickerSets();
|
||||
auto it = sets.constFind(setId);
|
||||
if (it == sets.cend() || (it->flags & MTPDstickerSet::Flag::f_disabled) || it->stickers.isEmpty()) return;
|
||||
if (it == sets.cend() || (it->flags & MTPDstickerSet::Flag::f_archived) || it->stickers.isEmpty()) return;
|
||||
|
||||
StickerPack pack;
|
||||
pack.reserve(it->stickers.size());
|
||||
|
@ -2110,37 +2111,49 @@ void StickerPanInner::refreshRecent() {
|
|||
void StickerPanInner::refreshRecentStickers(bool performResize) {
|
||||
_custom.clear();
|
||||
clearSelection(true);
|
||||
auto customIt = Global::StickerSets().constFind(Stickers::CustomSetId);
|
||||
if (cGetRecentStickers().isEmpty() && (customIt == Global::StickerSets().cend() || customIt->stickers.isEmpty())) {
|
||||
auto &sets = Global::StickerSets();
|
||||
auto &recent = cGetRecentStickers();
|
||||
auto customIt = sets.constFind(Stickers::CustomSetId);
|
||||
auto cloudIt = sets.constFind(Stickers::CloudRecentSetId);
|
||||
if (recent.isEmpty()
|
||||
&& (customIt == sets.cend() || customIt->stickers.isEmpty())
|
||||
&& (cloudIt == sets.cend() || cloudIt->stickers.isEmpty())) {
|
||||
if (!_sets.isEmpty() && _sets.at(0).id == Stickers::RecentSetId) {
|
||||
_sets.pop_front();
|
||||
}
|
||||
} else {
|
||||
StickerPack recent;
|
||||
int32 customCnt = (customIt == Global::StickerSets().cend() ? 0 : customIt->stickers.size());
|
||||
QMap<DocumentData*, bool> recentOnly;
|
||||
recent.reserve(cGetRecentStickers().size() + customCnt);
|
||||
_custom.reserve(cGetRecentStickers().size() + customCnt);
|
||||
for (int32 i = 0, l = cGetRecentStickers().size(); i < l; ++i) {
|
||||
DocumentData *s = cGetRecentStickers().at(i).first;
|
||||
recent.push_back(s);
|
||||
recentOnly.insert(s, true);
|
||||
StickerPack recentPack;
|
||||
int customCnt = (customIt == sets.cend()) ? 0 : customIt->stickers.size();
|
||||
int cloudCnt = (cloudIt == sets.cend()) ? 0 : cloudIt->stickers.size();
|
||||
recentPack.reserve(cloudCnt + recent.size() + customCnt);
|
||||
_custom.reserve(cloudCnt + recent.size() + customCnt);
|
||||
if (cloudCnt > 0) {
|
||||
for_const (auto sticker, cloudIt->stickers) {
|
||||
recentPack.push_back(sticker);
|
||||
_custom.push_back(false);
|
||||
}
|
||||
}
|
||||
for_const (auto &recentSticker, recent) {
|
||||
auto sticker = recentSticker.first;
|
||||
recentPack.push_back(sticker);
|
||||
_custom.push_back(false);
|
||||
}
|
||||
for (int32 i = 0; i < customCnt; ++i) {
|
||||
DocumentData *s = customIt->stickers.at(i);
|
||||
if (recentOnly.contains(s)) {
|
||||
_custom[recent.indexOf(s)] = true;
|
||||
} else {
|
||||
recent.push_back(s);
|
||||
_custom.push_back(true);
|
||||
if (customCnt > 0) {
|
||||
for_const (auto &sticker, customIt->stickers) {
|
||||
auto index = recentPack.indexOf(sticker);
|
||||
if (index >= cloudCnt) {
|
||||
_custom[index] = true; // mark stickers from recent as custom
|
||||
} else {
|
||||
recentPack.push_back(sticker);
|
||||
_custom.push_back(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (_sets.isEmpty() || _sets.at(0).id != Stickers::RecentSetId) {
|
||||
_sets.push_back(DisplayedSet(Stickers::RecentSetId, MTPDstickerSet::Flag::f_official, lang(lng_emoji_category0), recent.size() * 2, recent));
|
||||
_sets.push_back(DisplayedSet(Stickers::RecentSetId, MTPDstickerSet::Flag::f_official | MTPDstickerSet_ClientFlag::f_special, lang(lng_recent_stickers), recentPack.size() * 2, recentPack));
|
||||
} else {
|
||||
_sets[0].pack = recent;
|
||||
_sets[0].hovers.resize(recent.size() * 2);
|
||||
_sets[0].pack = recentPack;
|
||||
_sets[0].hovers.resize(recentPack.size() * 2);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2829,6 +2842,20 @@ void EmojiPan::onSaveConfigDelayed(int32 delay) {
|
|||
_saveConfigTimer.start(delay);
|
||||
}
|
||||
|
||||
void EmojiPan::paintStickerSettingsIcon(Painter &p) const {
|
||||
int settingsLeft = _iconsLeft + 7 * st::rbEmoji.width;
|
||||
p.drawSpriteLeft(settingsLeft + st::rbEmojiRecent.imagePos.x(), _iconsTop + st::rbEmojiRecent.imagePos.y(), width(), st::stickersSettings);
|
||||
if (auto unread = Global::FeaturedStickerSetsUnreadCount()) {
|
||||
Dialogs::Layout::UnreadBadgeStyle unreadSt;
|
||||
unreadSt.sizeId = Dialogs::Layout::UnreadBadgeInStickersPanel;
|
||||
unreadSt.size = st::stickersSettingsUnreadSize;
|
||||
int unreadRight = settingsLeft + st::rbEmoji.width - st::stickersSettingsUnreadPosition.x();
|
||||
if (rtl()) unreadRight = width() - unreadRight;
|
||||
int unreadTop = _iconsTop + st::stickersSettingsUnreadPosition.y();
|
||||
Dialogs::Layout::paintUnreadCount(p, QString::number(unread), unreadRight, unreadTop, unreadSt);
|
||||
}
|
||||
}
|
||||
|
||||
void EmojiPan::paintEvent(QPaintEvent *e) {
|
||||
Painter p(this);
|
||||
|
||||
|
@ -2846,7 +2873,7 @@ void EmojiPan::paintEvent(QPaintEvent *e) {
|
|||
p.fillRect(myrtlrect(r.x() + r.width() - st::emojiScroll.width, r.y(), st::emojiScroll.width, e_scroll.height()), st::white->b);
|
||||
if (_stickersShown && s_inner.showSectionIcons()) {
|
||||
p.fillRect(r.left(), _iconsTop, r.width(), st::rbEmoji.height, st::emojiPanCategories);
|
||||
p.drawSpriteLeft(_iconsLeft + 7 * st::rbEmoji.width + st::rbEmojiRecent.imagePos.x(), _iconsTop + st::rbEmojiRecent.imagePos.y(), width(), st::stickersSettings);
|
||||
paintStickerSettingsIcon(p);
|
||||
|
||||
if (!_icons.isEmpty()) {
|
||||
int32 x = _iconsLeft, i = 0, selxrel = _iconsLeft + _iconSelX.current(), selx = selxrel - _iconsX.current();
|
||||
|
@ -3093,6 +3120,7 @@ void EmojiPan::refreshStickers() {
|
|||
if (!_stickersShown) {
|
||||
s_inner.preloadImages();
|
||||
}
|
||||
update();
|
||||
}
|
||||
|
||||
void EmojiPan::refreshSavedGifs() {
|
||||
|
@ -3228,7 +3256,7 @@ void EmojiPan::step_icons(uint64 ms, bool timer) {
|
|||
}
|
||||
|
||||
if (_iconsStartAnim) {
|
||||
float64 dt = (ms - _iconsStartAnim) / st::stickerIconMove;
|
||||
float64 dt = (ms - _iconsStartAnim) / float64(st::stickerIconMove);
|
||||
if (dt >= 1) {
|
||||
_iconsStartAnim = 0;
|
||||
_iconsX.finish();
|
||||
|
@ -3584,7 +3612,7 @@ void EmojiPan::onSwitch() {
|
|||
} else {
|
||||
if (cShowingSavedGifs() && cSavedGifs().isEmpty()) {
|
||||
s_inner.showStickerSet(Stickers::DefaultSetId);
|
||||
} else if (!cShowingSavedGifs() && !cSavedGifs().isEmpty() && Global::StickerSets().isEmpty()) {
|
||||
} else if (!cShowingSavedGifs() && !cSavedGifs().isEmpty() && Global::StickerSetsOrder().isEmpty()) {
|
||||
s_inner.showStickerSet(Stickers::NoneSetId);
|
||||
} else {
|
||||
s_inner.updateShowingSavedGifs();
|
||||
|
@ -3623,8 +3651,9 @@ void EmojiPan::onSwitch() {
|
|||
}
|
||||
|
||||
void EmojiPan::onRemoveSet(quint64 setId) {
|
||||
auto it = Global::StickerSets().constFind(setId);
|
||||
if (it != Global::StickerSets().cend() && !(it->flags & MTPDstickerSet::Flag::f_official)) {
|
||||
auto &sets = Global::StickerSets();
|
||||
auto it = sets.constFind(setId);
|
||||
if (it != sets.cend() && !(it->flags & MTPDstickerSet::Flag::f_official)) {
|
||||
_removingSetId = it->id;
|
||||
ConfirmBox *box = new ConfirmBox(lng_stickers_remove_pack(lt_sticker_pack, it->title), lang(lng_box_remove));
|
||||
connect(box, SIGNAL(confirmed()), this, SLOT(onRemoveSetSure()));
|
||||
|
@ -3635,8 +3664,9 @@ void EmojiPan::onRemoveSet(quint64 setId) {
|
|||
|
||||
void EmojiPan::onRemoveSetSure() {
|
||||
Ui::hideLayer();
|
||||
auto it = Global::RefStickerSets().find(_removingSetId);
|
||||
if (it != Global::RefStickerSets().cend() && !(it->flags & MTPDstickerSet::Flag::f_official)) {
|
||||
auto &sets = Global::RefStickerSets();
|
||||
auto it = sets.find(_removingSetId);
|
||||
if (it != sets.cend() && !(it->flags & MTPDstickerSet::Flag::f_official)) {
|
||||
if (it->id && it->access) {
|
||||
MTP::send(MTPmessages_UninstallStickerSet(MTP_inputStickerSetID(MTP_long(it->id), MTP_long(it->access))));
|
||||
} else if (!it->shortName.isEmpty()) {
|
||||
|
@ -3652,11 +3682,14 @@ void EmojiPan::onRemoveSetSure() {
|
|||
++i;
|
||||
}
|
||||
}
|
||||
Global::RefStickerSets().erase(it);
|
||||
it->flags &= ~MTPDstickerSet::Flag::f_installed;
|
||||
if (!(it->flags & MTPDstickerSet_ClientFlag::f_featured) && !(it->flags & MTPDstickerSet_ClientFlag::f_special)) {
|
||||
sets.erase(it);
|
||||
}
|
||||
int removeIndex = Global::StickerSetsOrder().indexOf(_removingSetId);
|
||||
if (removeIndex >= 0) Global::RefStickerSetsOrder().removeAt(removeIndex);
|
||||
refreshStickers();
|
||||
Local::writeStickers();
|
||||
Local::writeInstalledStickers();
|
||||
if (writeRecent) Local::writeUserSettings();
|
||||
}
|
||||
_removingSetId = 0;
|
||||
|
|
|
@ -656,6 +656,7 @@ signals:
|
|||
void updateStickers();
|
||||
|
||||
private:
|
||||
void paintStickerSettingsIcon(Painter &p) const;
|
||||
|
||||
void validateSelectedIcon(bool animated = false);
|
||||
|
||||
|
|
|
@ -33,6 +33,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
|
||||
Q_DECLARE_METATYPE(ClickHandlerPtr);
|
||||
Q_DECLARE_METATYPE(Qt::MouseButton);
|
||||
Q_DECLARE_METATYPE(Ui::ShowWay);
|
||||
|
||||
namespace App {
|
||||
|
||||
|
@ -77,7 +78,7 @@ void activateBotCommand(const HistoryItem *msg, int row, int col) {
|
|||
|
||||
case HistoryMessageReplyMarkup::Button::Url: {
|
||||
auto url = QString::fromUtf8(button->data);
|
||||
HiddenUrlClickHandler(url).onClick(Qt::LeftButton);
|
||||
UrlClickHandler(url).onClick(Qt::LeftButton);
|
||||
} break;
|
||||
|
||||
case HistoryMessageReplyMarkup::Button::RequestLocation: {
|
||||
|
@ -253,13 +254,14 @@ void showPeerOverview(const PeerId &peer, MediaOverviewType type) {
|
|||
}
|
||||
}
|
||||
|
||||
void showPeerHistory(const PeerId &peer, MsgId msgId, bool back) {
|
||||
if (MainWidget *m = App::main()) m->ui_showPeerHistory(peer, msgId, back);
|
||||
void showPeerHistory(const PeerId &peer, MsgId msgId, ShowWay way) {
|
||||
if (MainWidget *m = App::main()) m->ui_showPeerHistory(peer, msgId, way);
|
||||
}
|
||||
|
||||
void showPeerHistoryAsync(const PeerId &peer, MsgId msgId) {
|
||||
void showPeerHistoryAsync(const PeerId &peer, MsgId msgId, ShowWay way) {
|
||||
if (MainWidget *m = App::main()) {
|
||||
QMetaObject::invokeMethod(m, "ui_showPeerHistoryAsync", Qt::QueuedConnection, Q_ARG(quint64, peer), Q_ARG(qint32, msgId));
|
||||
qRegisterMetaType<Ui::ShowWay>();
|
||||
QMetaObject::invokeMethod(m, "ui_showPeerHistoryAsync", Qt::QueuedConnection, Q_ARG(quint64, peer), Q_ARG(qint32, msgId), Q_ARG(Ui::ShowWay, way));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -518,6 +520,45 @@ DefineVar(Sandbox, ConnectionProxy, PreLaunchProxy);
|
|||
|
||||
} // namespace Sandbox
|
||||
|
||||
namespace Stickers {
|
||||
|
||||
Set *feedSet(const MTPDstickerSet &set) {
|
||||
MTPDstickerSet::Flags flags = 0;
|
||||
|
||||
auto &sets = Global::RefStickerSets();
|
||||
auto it = sets.find(set.vid.v);
|
||||
auto title = stickerSetTitle(set);
|
||||
if (it == sets.cend()) {
|
||||
it = sets.insert(set.vid.v, Stickers::Set(set.vid.v, set.vaccess_hash.v, title, qs(set.vshort_name), set.vcount.v, set.vhash.v, set.vflags.v | MTPDstickerSet_ClientFlag::f_not_loaded));
|
||||
} else {
|
||||
it->access = set.vaccess_hash.v;
|
||||
it->title = title;
|
||||
it->shortName = qs(set.vshort_name);
|
||||
flags = it->flags;
|
||||
auto clientFlags = it->flags & (MTPDstickerSet_ClientFlag::f_featured | MTPDstickerSet_ClientFlag::f_unread | MTPDstickerSet_ClientFlag::f_not_loaded | MTPDstickerSet_ClientFlag::f_special);
|
||||
it->flags = set.vflags.v | clientFlags;
|
||||
if (it->count != set.vcount.v || it->hash != set.vhash.v || it->emoji.isEmpty()) {
|
||||
it->count = set.vcount.v;
|
||||
it->hash = set.vhash.v;
|
||||
it->flags |= MTPDstickerSet_ClientFlag::f_not_loaded; // need to request this set
|
||||
}
|
||||
}
|
||||
auto changedFlags = (flags ^ it->flags);
|
||||
if (changedFlags & MTPDstickerSet::Flag::f_archived) {
|
||||
auto index = Global::ArchivedStickerSetsOrder().indexOf(it->id);
|
||||
if (it->flags & MTPDstickerSet::Flag::f_archived) {
|
||||
if (index < 0) {
|
||||
Global::RefArchivedStickerSetsOrder().push_front(it->id);
|
||||
}
|
||||
} else if (index >= 0) {
|
||||
Global::RefArchivedStickerSetsOrder().removeAt(index);
|
||||
}
|
||||
}
|
||||
return &it.value();
|
||||
}
|
||||
|
||||
} // namespace Stickers
|
||||
|
||||
namespace Global {
|
||||
namespace internal {
|
||||
|
||||
|
@ -538,6 +579,9 @@ struct Data {
|
|||
|
||||
int32 DebugLoggingFlags = 0;
|
||||
|
||||
float64 SongVolume = 0.9;
|
||||
float64 VideoVolume = 0.9;
|
||||
|
||||
// config
|
||||
int32 ChatSizeMax = 200;
|
||||
int32 MegagroupSizeMax = 1000;
|
||||
|
@ -554,6 +598,7 @@ struct Data {
|
|||
int32 PushChatLimit = 2;
|
||||
int32 SavedGifsLimit = 200;
|
||||
int32 EditTimeLimit = 172800;
|
||||
int32 StickersRecentLimit = 30;
|
||||
|
||||
HiddenPinnedMessagesMap HiddenPinnedMessages;
|
||||
|
||||
|
@ -562,6 +607,11 @@ struct Data {
|
|||
Stickers::Sets StickerSets;
|
||||
Stickers::Order StickerSetsOrder;
|
||||
uint64 LastStickersUpdate = 0;
|
||||
uint64 LastRecentStickersUpdate = 0;
|
||||
Stickers::Order FeaturedStickerSetsOrder;
|
||||
int FeaturedStickerSetsUnreadCount = 0;
|
||||
uint64 LastFeaturedStickersUpdate = 0;
|
||||
Stickers::Order ArchivedStickerSetsOrder;
|
||||
|
||||
MTP::DcOptions DcOptions;
|
||||
|
||||
|
@ -606,6 +656,9 @@ DefineVar(Global, bool, ScreenIsLocked);
|
|||
|
||||
DefineVar(Global, int32, DebugLoggingFlags);
|
||||
|
||||
DefineVar(Global, float64, SongVolume);
|
||||
DefineVar(Global, float64, VideoVolume);
|
||||
|
||||
// config
|
||||
DefineVar(Global, int32, ChatSizeMax);
|
||||
DefineVar(Global, int32, MegagroupSizeMax);
|
||||
|
@ -622,6 +675,7 @@ DefineVar(Global, int32, PushChatPeriod);
|
|||
DefineVar(Global, int32, PushChatLimit);
|
||||
DefineVar(Global, int32, SavedGifsLimit);
|
||||
DefineVar(Global, int32, EditTimeLimit);
|
||||
DefineVar(Global, int32, StickersRecentLimit);
|
||||
|
||||
DefineVar(Global, HiddenPinnedMessagesMap, HiddenPinnedMessages);
|
||||
|
||||
|
@ -630,6 +684,11 @@ DefineRefVar(Global, PendingItemsMap, PendingRepaintItems);
|
|||
DefineVar(Global, Stickers::Sets, StickerSets);
|
||||
DefineVar(Global, Stickers::Order, StickerSetsOrder);
|
||||
DefineVar(Global, uint64, LastStickersUpdate);
|
||||
DefineVar(Global, uint64, LastRecentStickersUpdate);
|
||||
DefineVar(Global, Stickers::Order, FeaturedStickerSetsOrder);
|
||||
DefineVar(Global, int, FeaturedStickerSetsUnreadCount);
|
||||
DefineVar(Global, uint64, LastFeaturedStickersUpdate);
|
||||
DefineVar(Global, Stickers::Order, ArchivedStickerSetsOrder);
|
||||
|
||||
DefineVar(Global, MTP::DcOptions, DcOptions);
|
||||
|
||||
|
|
|
@ -83,22 +83,27 @@ inline void showPeerOverview(const History *history, MediaOverviewType type) {
|
|||
showPeerOverview(history->peer->id, type);
|
||||
}
|
||||
|
||||
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);
|
||||
enum class ShowWay {
|
||||
ClearStack,
|
||||
Forward,
|
||||
Backward,
|
||||
};
|
||||
void showPeerHistory(const PeerId &peer, MsgId msgId, ShowWay way = ShowWay::ClearStack);
|
||||
inline void showPeerHistory(const PeerData *peer, MsgId msgId, ShowWay way = ShowWay::ClearStack) {
|
||||
showPeerHistory(peer->id, msgId, way);
|
||||
}
|
||||
inline void showPeerHistory(const History *history, MsgId msgId, bool back = false) {
|
||||
showPeerHistory(history->peer->id, msgId, back);
|
||||
inline void showPeerHistory(const History *history, MsgId msgId, ShowWay way = ShowWay::ClearStack) {
|
||||
showPeerHistory(history->peer->id, msgId, way);
|
||||
}
|
||||
inline void showPeerHistoryAtItem(const HistoryItem *item) {
|
||||
showPeerHistory(item->history()->peer->id, item->id);
|
||||
inline void showPeerHistoryAtItem(const HistoryItem *item, ShowWay way = ShowWay::ClearStack) {
|
||||
showPeerHistory(item->history()->peer->id, item->id, way);
|
||||
}
|
||||
void showPeerHistoryAsync(const PeerId &peer, MsgId msgId);
|
||||
void showPeerHistoryAsync(const PeerId &peer, MsgId msgId, ShowWay way = ShowWay::ClearStack);
|
||||
inline void showChatsList() {
|
||||
showPeerHistory(PeerId(0), 0);
|
||||
showPeerHistory(PeerId(0), 0, ShowWay::ClearStack);
|
||||
}
|
||||
inline void showChatsListAsync() {
|
||||
showPeerHistoryAsync(PeerId(0), 0);
|
||||
showPeerHistoryAsync(PeerId(0), 0, ShowWay::ClearStack);
|
||||
}
|
||||
PeerData *getPeerForMouseAction();
|
||||
|
||||
|
@ -178,10 +183,19 @@ enum Flags {
|
|||
namespace Stickers {
|
||||
|
||||
static const uint64 DefaultSetId = 0; // for backward compatibility
|
||||
static const uint64 CustomSetId = 0xFFFFFFFFFFFFFFFFULL, RecentSetId = 0xFFFFFFFFFFFFFFFEULL;
|
||||
static const uint64 NoneSetId = 0xFFFFFFFFFFFFFFFDULL; // for emoji/stickers panel
|
||||
static const uint64 CustomSetId = 0xFFFFFFFFFFFFFFFFULL;
|
||||
static const uint64 RecentSetId = 0xFFFFFFFFFFFFFFFEULL; // for emoji/stickers panel, should not appear in Sets
|
||||
static const uint64 NoneSetId = 0xFFFFFFFFFFFFFFFDULL; // for emoji/stickers panel, should not appear in Sets
|
||||
static const uint64 CloudRecentSetId = 0xFFFFFFFFFFFFFFFCULL; // for cloud-stored recent stickers
|
||||
struct Set {
|
||||
Set(uint64 id, uint64 access, const QString &title, const QString &shortName, int32 count, int32 hash, MTPDstickerSet::Flags flags) : id(id), access(access), title(title), shortName(shortName), count(count), hash(hash), flags(flags) {
|
||||
Set(uint64 id, uint64 access, const QString &title, const QString &shortName, int32 count, int32 hash, MTPDstickerSet::Flags flags)
|
||||
: id(id)
|
||||
, access(access)
|
||||
, title(title)
|
||||
, shortName(shortName)
|
||||
, count(count)
|
||||
, hash(hash)
|
||||
, flags(flags) {
|
||||
}
|
||||
uint64 id, access;
|
||||
QString title, shortName;
|
||||
|
@ -193,6 +207,15 @@ struct Set {
|
|||
using Sets = QMap<uint64, Set>;
|
||||
using Order = QList<uint64>;
|
||||
|
||||
inline MTPInputStickerSet inputSetId(const Set &set) {
|
||||
if (set.id && set.access) {
|
||||
return MTP_inputStickerSetID(MTP_long(set.id), MTP_long(set.access));
|
||||
}
|
||||
return MTP_inputStickerSetShortName(MTP_string(set.shortName));
|
||||
}
|
||||
|
||||
Set *feedSet(const MTPDstickerSet &set);
|
||||
|
||||
} // namespace Stickers
|
||||
|
||||
namespace Global {
|
||||
|
@ -217,6 +240,9 @@ DeclareVar(bool, ScreenIsLocked);
|
|||
|
||||
DeclareVar(int32, DebugLoggingFlags);
|
||||
|
||||
DeclareVar(float64, SongVolume);
|
||||
DeclareVar(float64, VideoVolume);
|
||||
|
||||
// config
|
||||
DeclareVar(int32, ChatSizeMax);
|
||||
DeclareVar(int32, MegagroupSizeMax);
|
||||
|
@ -233,6 +259,7 @@ DeclareVar(int32, PushChatPeriod);
|
|||
DeclareVar(int32, PushChatLimit);
|
||||
DeclareVar(int32, SavedGifsLimit);
|
||||
DeclareVar(int32, EditTimeLimit);
|
||||
DeclareVar(int32, StickersRecentLimit);
|
||||
|
||||
typedef QMap<PeerId, MsgId> HiddenPinnedMessagesMap;
|
||||
DeclareVar(HiddenPinnedMessagesMap, HiddenPinnedMessages);
|
||||
|
@ -243,6 +270,11 @@ DeclareRefVar(PendingItemsMap, PendingRepaintItems);
|
|||
DeclareVar(Stickers::Sets, StickerSets);
|
||||
DeclareVar(Stickers::Order, StickerSetsOrder);
|
||||
DeclareVar(uint64, LastStickersUpdate);
|
||||
DeclareVar(uint64, LastRecentStickersUpdate);
|
||||
DeclareVar(Stickers::Order, FeaturedStickerSetsOrder);
|
||||
DeclareVar(int, FeaturedStickerSetsUnreadCount);
|
||||
DeclareVar(uint64, LastFeaturedStickersUpdate);
|
||||
DeclareVar(Stickers::Order, ArchivedStickerSetsOrder);
|
||||
|
||||
DeclareVar(MTP::DcOptions, DcOptions);
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
#include "styles/style_dialogs.h"
|
||||
#include "history/history_service_layout.h"
|
||||
#include "data/data_drafts.h"
|
||||
#include "media/media_clip_reader.h"
|
||||
#include "lang.h"
|
||||
#include "mainwidget.h"
|
||||
#include "application.h"
|
||||
|
@ -34,7 +35,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
#include "ui/filedialog.h"
|
||||
#include "boxes/addcontactbox.h"
|
||||
#include "boxes/confirmbox.h"
|
||||
#include "audio.h"
|
||||
#include "media/media_audio.h"
|
||||
#include "localstorage.h"
|
||||
#include "apiwrap.h"
|
||||
#include "window/top_bar_widget.h"
|
||||
|
@ -150,6 +151,9 @@ void History::clearLastKeyboard() {
|
|||
lastKeyboardHiddenId = 0;
|
||||
}
|
||||
lastKeyboardId = 0;
|
||||
if (auto main = App::main()) {
|
||||
main->updateBotKeyboard(this);
|
||||
}
|
||||
}
|
||||
lastKeyboardInited = true;
|
||||
lastKeyboardFrom = 0;
|
||||
|
@ -838,7 +842,6 @@ HistoryItem *History::createItem(const MTPMessage &msg, bool applyServiceAction,
|
|||
PeerId uid = peerFromUser(d.vuser_id);
|
||||
if (lastKeyboardFrom == uid) {
|
||||
clearLastKeyboard();
|
||||
if (App::main()) App::main()->updateBotKeyboard(this);
|
||||
}
|
||||
if (peer->isMegagroup()) {
|
||||
if (auto user = App::userLoaded(uid)) {
|
||||
|
@ -2718,7 +2721,6 @@ void HistoryItem::finishEditionToEmpty() {
|
|||
}
|
||||
if (history()->lastKeyboardId == id) {
|
||||
history()->clearLastKeyboard();
|
||||
if (App::main()) App::main()->updateBotKeyboard(history());
|
||||
}
|
||||
if ((!out() || isPost()) && unread() && history()->unreadCount() > 0) {
|
||||
history()->setUnreadCount(history()->unreadCount() - 1);
|
||||
|
@ -2766,7 +2768,6 @@ void HistoryItem::destroy() {
|
|||
}
|
||||
if (history()->lastKeyboardId == id) {
|
||||
history()->clearLastKeyboard();
|
||||
if (App::main()) App::main()->updateBotKeyboard(history());
|
||||
}
|
||||
if ((!out() || isPost()) && unread() && history()->unreadCount() > 0) {
|
||||
history()->setUnreadCount(history()->unreadCount() - 1);
|
||||
|
@ -2832,7 +2833,9 @@ void HistoryItem::setId(MsgId newId) {
|
|||
}
|
||||
|
||||
bool HistoryItem::canEdit(const QDateTime &cur) const {
|
||||
if (id < 0 || date.secsTo(cur) >= Global::EditTimeLimit()) return false;
|
||||
auto messageToMyself = (peerToUser(_history->peer->id) == MTP::authedId());
|
||||
auto messageTooOld = messageToMyself ? false : (date.secsTo(cur) >= Global::EditTimeLimit());
|
||||
if (id < 0 || messageTooOld) return false;
|
||||
|
||||
if (auto msg = toHistoryMessage()) {
|
||||
if (msg->Has<HistoryMessageVia>() || msg->Has<HistoryMessageForwarded>()) return false;
|
||||
|
@ -2853,7 +2856,7 @@ bool HistoryItem::canEdit(const QDateTime &cur) const {
|
|||
auto channel = _history->peer->asChannel();
|
||||
return (channel->amCreator() || (channel->amEditor() && out()));
|
||||
}
|
||||
return out() || (peerToUser(_history->peer->id) == MTP::authedId());
|
||||
return out() || messageToMyself;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -2868,7 +2871,9 @@ bool HistoryItem::unread() const {
|
|||
|
||||
if (id > 0) {
|
||||
if (id < history()->outboxReadBefore) return false;
|
||||
if (auto channel = history()->peer->asChannel()) {
|
||||
if (auto user = history()->peer->asUser()) {
|
||||
if (user->botInfo) return false;
|
||||
} else if (auto channel = history()->peer->asChannel()) {
|
||||
if (!channel->isMegagroup()) return false;
|
||||
}
|
||||
}
|
||||
|
@ -2923,17 +2928,19 @@ void HistoryItem::setUnreadBarFreezed() {
|
|||
}
|
||||
}
|
||||
|
||||
void HistoryItem::clipCallback(ClipReaderNotification notification) {
|
||||
void HistoryItem::clipCallback(Media::Clip::Notification notification) {
|
||||
using namespace Media::Clip;
|
||||
|
||||
HistoryMedia *media = getMedia();
|
||||
if (!media) return;
|
||||
|
||||
ClipReader *reader = media ? media->getClipReader() : 0;
|
||||
Reader *reader = media ? media->getClipReader() : 0;
|
||||
if (!reader) return;
|
||||
|
||||
switch (notification) {
|
||||
case ClipReaderReinit: {
|
||||
case NotificationReinit: {
|
||||
bool stopped = false;
|
||||
if (reader->paused()) {
|
||||
if (reader->autoPausedGif()) {
|
||||
if (MainWidget *m = App::main()) {
|
||||
if (!m->isItemVisible(this)) { // stop animation if it is not visible
|
||||
media->stopInline();
|
||||
|
@ -2950,7 +2957,7 @@ void HistoryItem::clipCallback(ClipReaderNotification notification) {
|
|||
}
|
||||
} break;
|
||||
|
||||
case ClipReaderRepaint: {
|
||||
case NotificationRepaint: {
|
||||
if (!reader->currentDisplayed()) {
|
||||
Ui::repaintHistoryItem(this);
|
||||
}
|
||||
|
@ -3405,11 +3412,13 @@ void HistoryPhoto::draw(Painter &p, const QRect &r, TextSelection selection, uin
|
|||
App::roundShadow(p, 0, 0, width, height, selected ? st::msgInShadowSelected : st::msgInShadow, selected ? InSelectedShadowCorners : InShadowCorners);
|
||||
}
|
||||
|
||||
auto inWebPage = (_parent->getMedia() != this);
|
||||
auto roundRadius = inWebPage ? ImageRoundRadius::Small : ImageRoundRadius::Large;
|
||||
QPixmap pix;
|
||||
if (loaded) {
|
||||
pix = _data->full->pixSingle(ImageRoundRadius::Large, _pixw, _pixh, width, height);
|
||||
pix = _data->full->pixSingle(roundRadius, _pixw, _pixh, width, height);
|
||||
} else {
|
||||
pix = _data->thumb->pixBlurredSingle(ImageRoundRadius::Large, _pixw, _pixh, width, height);
|
||||
pix = _data->thumb->pixBlurredSingle(roundRadius, _pixw, _pixh, width, height);
|
||||
}
|
||||
QRect rthumb(rtlrect(skipx, skipy, width, height, _width));
|
||||
p.drawPixmap(rthumb.topLeft(), pix);
|
||||
|
@ -4074,10 +4083,10 @@ void HistoryDocument::draw(Painter &p, const QRect &r, TextSelection selection,
|
|||
bottom = st::msgFileThumbPadding.top() + st::msgFileThumbSize + st::msgFileThumbPadding.bottom();
|
||||
|
||||
QRect rthumb(rtlrect(st::msgFileThumbPadding.left(), st::msgFileThumbPadding.top(), st::msgFileThumbSize, st::msgFileThumbSize, _width));
|
||||
QPixmap thumb = loaded ? _data->thumb->pixSingle(ImageRoundRadius::Small, thumbed->_thumbw, 0, st::msgFileThumbSize, st::msgFileThumbSize) : _data->thumb->pixBlurredSingle(ImageRoundRadius::Small, thumbed->_thumbw, 0, st::msgFileThumbSize, st::msgFileThumbSize);
|
||||
QPixmap thumb = loaded ? _data->thumb->pixSingle(ImageRoundRadius::Large, thumbed->_thumbw, 0, st::msgFileThumbSize, st::msgFileThumbSize) : _data->thumb->pixBlurredSingle(ImageRoundRadius::Small, thumbed->_thumbw, 0, st::msgFileThumbSize, st::msgFileThumbSize);
|
||||
p.drawPixmap(rthumb.topLeft(), thumb);
|
||||
if (selected) {
|
||||
App::roundRect(p, rthumb, textstyleCurrent()->selectOverlay, SelectedOverlaySmallCorners);
|
||||
App::roundRect(p, rthumb, textstyleCurrent()->selectOverlay, SelectedOverlayLargeCorners);
|
||||
}
|
||||
|
||||
if (radial || (!loaded && !_data->loading())) {
|
||||
|
@ -4399,61 +4408,48 @@ bool HistoryDocument::updateStatusText() const {
|
|||
} else if (_data->loading()) {
|
||||
statusSize = _data->loadOffset();
|
||||
} else if (_data->loaded()) {
|
||||
if (_data->voice()) {
|
||||
AudioMsgId playing;
|
||||
AudioPlayerState playingState = AudioPlayerStopped;
|
||||
int64 playingPosition = 0, playingDuration = 0;
|
||||
int32 playingFrequency = 0;
|
||||
if (audioPlayer()) {
|
||||
audioPlayer()->currentState(&playing, &playingState, &playingPosition, &playingDuration, &playingFrequency);
|
||||
}
|
||||
|
||||
if (playing == AudioMsgId(_data, _parent->fullId()) && !(playingState & AudioPlayerStoppedMask) && playingState != AudioPlayerFinishing) {
|
||||
if (auto voice = Get<HistoryDocumentVoice>()) {
|
||||
bool was = voice->_playback;
|
||||
voice->ensurePlayback(this);
|
||||
if (!was || playingPosition != voice->_playback->_position) {
|
||||
float64 prg = playingDuration ? snap(float64(playingPosition) / playingDuration, 0., 1.) : 0.;
|
||||
if (voice->_playback->_position < playingPosition) {
|
||||
voice->_playback->a_progress.start(prg);
|
||||
} else {
|
||||
voice->_playback->a_progress = anim::fvalue(0., prg);
|
||||
statusSize = FileStatusSizeLoaded;
|
||||
if (audioPlayer()) {
|
||||
if (_data->voice()) {
|
||||
AudioMsgId playing;
|
||||
auto playbackState = audioPlayer()->currentState(&playing, AudioMsgId::Type::Voice);
|
||||
if (playing == AudioMsgId(_data, _parent->fullId()) && !(playbackState.state & AudioPlayerStoppedMask) && playbackState.state != AudioPlayerFinishing) {
|
||||
if (auto voice = Get<HistoryDocumentVoice>()) {
|
||||
bool was = voice->_playback;
|
||||
voice->ensurePlayback(this);
|
||||
if (!was || playbackState.position != voice->_playback->_position) {
|
||||
float64 prg = playbackState.duration ? snap(float64(playbackState.position) / playbackState.duration, 0., 1.) : 0.;
|
||||
if (voice->_playback->_position < playbackState.position) {
|
||||
voice->_playback->a_progress.start(prg);
|
||||
} else {
|
||||
voice->_playback->a_progress = anim::fvalue(0., prg);
|
||||
}
|
||||
voice->_playback->_position = playbackState.position;
|
||||
voice->_playback->_a_progress.start();
|
||||
}
|
||||
voice->_playback->_position = playingPosition;
|
||||
voice->_playback->_a_progress.start();
|
||||
}
|
||||
|
||||
statusSize = -1 - (playbackState.position / (playbackState.frequency ? playbackState.frequency : AudioVoiceMsgFrequency));
|
||||
realDuration = playbackState.duration / (playbackState.frequency ? playbackState.frequency : AudioVoiceMsgFrequency);
|
||||
showPause = (playbackState.state == AudioPlayerPlaying || playbackState.state == AudioPlayerResuming || playbackState.state == AudioPlayerStarting);
|
||||
} else {
|
||||
if (auto voice = Get<HistoryDocumentVoice>()) {
|
||||
voice->checkPlaybackFinished();
|
||||
}
|
||||
}
|
||||
|
||||
statusSize = -1 - (playingPosition / (playingFrequency ? playingFrequency : AudioVoiceMsgFrequency));
|
||||
realDuration = playingDuration / (playingFrequency ? playingFrequency : AudioVoiceMsgFrequency);
|
||||
showPause = (playingState == AudioPlayerPlaying || playingState == AudioPlayerResuming || playingState == AudioPlayerStarting);
|
||||
} else {
|
||||
statusSize = FileStatusSizeLoaded;
|
||||
if (auto voice = Get<HistoryDocumentVoice>()) {
|
||||
voice->checkPlaybackFinished();
|
||||
} else if (_data->song()) {
|
||||
AudioMsgId playing;
|
||||
auto playbackState = audioPlayer()->currentState(&playing, AudioMsgId::Type::Song);
|
||||
if (playing == AudioMsgId(_data, _parent->fullId()) && !(playbackState.state & AudioPlayerStoppedMask) && playbackState.state != AudioPlayerFinishing) {
|
||||
statusSize = -1 - (playbackState.position / (playbackState.frequency ? playbackState.frequency : AudioVoiceMsgFrequency));
|
||||
realDuration = playbackState.duration / (playbackState.frequency ? playbackState.frequency : AudioVoiceMsgFrequency);
|
||||
showPause = (playbackState.state == AudioPlayerPlaying || playbackState.state == AudioPlayerResuming || playbackState.state == AudioPlayerStarting);
|
||||
} else {
|
||||
}
|
||||
if (!showPause && (playing == AudioMsgId(_data, _parent->fullId())) && App::main() && App::main()->player()->seekingSong(playing)) {
|
||||
showPause = true;
|
||||
}
|
||||
}
|
||||
} else if (_data->song()) {
|
||||
SongMsgId playing;
|
||||
AudioPlayerState playingState = AudioPlayerStopped;
|
||||
int64 playingPosition = 0, playingDuration = 0;
|
||||
int32 playingFrequency = 0;
|
||||
if (audioPlayer()) {
|
||||
audioPlayer()->currentState(&playing, &playingState, &playingPosition, &playingDuration, &playingFrequency);
|
||||
}
|
||||
|
||||
if (playing == SongMsgId(_data, _parent->fullId()) && !(playingState & AudioPlayerStoppedMask) && playingState != AudioPlayerFinishing) {
|
||||
statusSize = -1 - (playingPosition / (playingFrequency ? playingFrequency : AudioVoiceMsgFrequency));
|
||||
realDuration = playingDuration / (playingFrequency ? playingFrequency : AudioVoiceMsgFrequency);
|
||||
showPause = (playingState == AudioPlayerPlaying || playingState == AudioPlayerResuming || playingState == AudioPlayerStarting);
|
||||
} else {
|
||||
statusSize = FileStatusSizeLoaded;
|
||||
}
|
||||
if (!showPause && (playing == SongMsgId(_data, _parent->fullId())) && App::main() && App::main()->player()->seekingSong(playing)) {
|
||||
showPause = true;
|
||||
}
|
||||
} else {
|
||||
statusSize = FileStatusSizeLoaded;
|
||||
}
|
||||
} else {
|
||||
statusSize = FileStatusSizeReady;
|
||||
|
@ -4543,13 +4539,13 @@ void HistoryGif::initDimensions() {
|
|||
|
||||
bool bubble = _parent->hasBubble();
|
||||
int32 tw = 0, th = 0;
|
||||
if (gif() && _gif->state() == ClipError) {
|
||||
if (gif() && _gif->state() == Media::Clip::State::Error) {
|
||||
if (!_gif->autoplay()) {
|
||||
Ui::showLayer(new InformBox(lang(lng_gif_error)));
|
||||
}
|
||||
App::unregGifItem(_gif);
|
||||
delete _gif;
|
||||
_gif = BadClipReader;
|
||||
_gif = Media::Clip::BadReader;
|
||||
}
|
||||
|
||||
if (gif() && _gif->ready()) {
|
||||
|
@ -4631,7 +4627,9 @@ int HistoryGif::resizeGetHeight(int width) {
|
|||
_width = qMax(_width, _parent->infoWidth() + 2 * int32(st::msgDateImgDelta + st::msgDateImgPadding.x()));
|
||||
if (gif() && _gif->ready()) {
|
||||
if (!_gif->started()) {
|
||||
_gif->start(_thumbw, _thumbh, _width, _height, true);
|
||||
auto inWebPage = (_parent->getMedia() != this);
|
||||
auto roundRadius = inWebPage ? ImageRoundRadius::Small : ImageRoundRadius::Large;
|
||||
_gif->start(_thumbw, _thumbh, _width, _height, roundRadius);
|
||||
}
|
||||
} else {
|
||||
_width = qMax(_width, gifMaxStatusWidth(_data) + 2 * int32(st::msgDateImgDelta + st::msgDateImgPadding.x()));
|
||||
|
@ -4654,7 +4652,7 @@ void HistoryGif::draw(Painter &p, const QRect &r, TextSelection selection, uint6
|
|||
bool loaded = _data->loaded(), displayLoading = (_parent->id < 0) || _data->displayLoading();
|
||||
bool selected = (selection == FullSelection);
|
||||
|
||||
if (loaded && !gif() && _gif != BadClipReader && cAutoPlayGif()) {
|
||||
if (loaded && !gif() && _gif != Media::Clip::BadReader && cAutoPlayGif()) {
|
||||
Ui::autoplayMediaInlineAsync(_parent->fullId());
|
||||
}
|
||||
|
||||
|
@ -4701,7 +4699,7 @@ void HistoryGif::draw(Painter &p, const QRect &r, TextSelection selection, uint6
|
|||
App::roundRect(p, rthumb, textstyleCurrent()->selectOverlay, SelectedOverlayLargeCorners);
|
||||
}
|
||||
|
||||
if (radial || (!_gif && ((!loaded && !_data->loading()) || !cAutoPlayGif())) || (_gif == BadClipReader)) {
|
||||
if (radial || (!_gif && ((!loaded && !_data->loading()) || !cAutoPlayGif())) || (_gif == Media::Clip::BadReader)) {
|
||||
float64 radialOpacity = (radial && loaded && _parent->id > 0) ? _animation->radial.opacity() : 1;
|
||||
QRect inner(rthumb.x() + (rthumb.width() - st::msgFileSize) / 2, rthumb.y() + (rthumb.height() - st::msgFileSize) / 2, st::msgFileSize, st::msgFileSize);
|
||||
p.setPen(Qt::NoPen);
|
||||
|
@ -4865,7 +4863,7 @@ bool HistoryGif::playInline(bool autoplay) {
|
|||
if (!cAutoPlayGif()) {
|
||||
App::stopGifItems();
|
||||
}
|
||||
_gif = new ClipReader(_data->location(), _data->data(), func(_parent, &HistoryItem::clipCallback));
|
||||
_gif = new Media::Clip::Reader(_data->location(), _data->data(), func(_parent, &HistoryItem::clipCallback));
|
||||
App::regGifItem(_gif, _parent);
|
||||
if (gif()) _gif->setAutoplay();
|
||||
}
|
||||
|
@ -5176,7 +5174,7 @@ int HistorySticker::additionalWidth(const HistoryMessageVia *via, const HistoryM
|
|||
}
|
||||
|
||||
void SendMessageClickHandler::onClickImpl() const {
|
||||
Ui::showPeerHistory(peer()->id, ShowAtUnreadMsgId);
|
||||
Ui::showPeerHistory(peer()->id, ShowAtUnreadMsgId, Ui::ShowWay::Forward);
|
||||
}
|
||||
|
||||
void AddContactClickHandler::onClickImpl() const {
|
||||
|
@ -5867,7 +5865,7 @@ HistoryWebPage::~HistoryWebPage() {
|
|||
}
|
||||
|
||||
namespace {
|
||||
LocationManager manager;
|
||||
LocationManager *locationManager = nullptr;
|
||||
}
|
||||
|
||||
void LocationManager::init() {
|
||||
|
@ -5879,13 +5877,16 @@ void LocationManager::init() {
|
|||
connect(manager, SIGNAL(sslErrors(QNetworkReply*, const QList<QSslError>&)), this, SLOT(onFailed(QNetworkReply*)));
|
||||
connect(manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(onFinished(QNetworkReply*)));
|
||||
|
||||
if (black) delete black;
|
||||
if (black) {
|
||||
delete black->v();
|
||||
delete black;
|
||||
}
|
||||
QImage b(cIntRetinaFactor(), cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied);
|
||||
{
|
||||
QPainter p(&b);
|
||||
p.fillRect(QRect(0, 0, cIntRetinaFactor(), cIntRetinaFactor()), st::white->b);
|
||||
}
|
||||
QPixmap p = QPixmap::fromImage(b, Qt::ColorOnly);
|
||||
QPixmap p = App::pixmapFromImageInPlace(std_::move(b));
|
||||
p.setDevicePixelRatio(cRetinaFactor());
|
||||
black = new ImagePtr(p, "PNG");
|
||||
}
|
||||
|
@ -5897,26 +5898,36 @@ void LocationManager::reinit() {
|
|||
void LocationManager::deinit() {
|
||||
if (manager) {
|
||||
delete manager;
|
||||
manager = 0;
|
||||
manager = nullptr;
|
||||
}
|
||||
if (black) {
|
||||
delete black->v();
|
||||
delete black;
|
||||
black = 0;
|
||||
black = nullptr;
|
||||
}
|
||||
dataLoadings.clear();
|
||||
imageLoadings.clear();
|
||||
}
|
||||
|
||||
void initImageLinkManager() {
|
||||
manager.init();
|
||||
if (!locationManager) {
|
||||
locationManager = new LocationManager();
|
||||
locationManager->init();
|
||||
}
|
||||
}
|
||||
|
||||
void reinitImageLinkManager() {
|
||||
manager.reinit();
|
||||
if (locationManager) {
|
||||
locationManager->reinit();
|
||||
}
|
||||
}
|
||||
|
||||
void deinitImageLinkManager() {
|
||||
manager.deinit();
|
||||
if (locationManager) {
|
||||
locationManager->deinit();
|
||||
delete locationManager;
|
||||
locationManager = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void LocationManager::getData(LocationData *data) {
|
||||
|
@ -6056,7 +6067,9 @@ void LocationData::load() {
|
|||
if (loading) return;
|
||||
|
||||
loading = true;
|
||||
manager.getData(this);
|
||||
if (locationManager) {
|
||||
locationManager->getData(this);
|
||||
}
|
||||
}
|
||||
|
||||
HistoryLocation::HistoryLocation(HistoryItem *parent, const LocationCoords &coords, const QString &title, const QString &description) : HistoryMedia(parent)
|
||||
|
|
|
@ -1412,7 +1412,7 @@ public:
|
|||
return _text.isEmpty() && !_media;
|
||||
}
|
||||
|
||||
void clipCallback(ClipReaderNotification notification);
|
||||
void clipCallback(Media::Clip::Notification notification);
|
||||
|
||||
virtual ~HistoryItem();
|
||||
|
||||
|
@ -1656,7 +1656,7 @@ public:
|
|||
virtual DocumentData *getDocument() {
|
||||
return nullptr;
|
||||
}
|
||||
virtual ClipReader *getClipReader() {
|
||||
virtual Media::Clip::Reader *getClipReader() {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
@ -2140,7 +2140,7 @@ public:
|
|||
DocumentData *getDocument() override {
|
||||
return _data;
|
||||
}
|
||||
ClipReader *getClipReader() override {
|
||||
Media::Clip::Reader *getClipReader() override {
|
||||
return gif();
|
||||
}
|
||||
|
||||
|
@ -2189,12 +2189,12 @@ private:
|
|||
int32 _thumbw, _thumbh;
|
||||
Text _caption;
|
||||
|
||||
ClipReader *_gif;
|
||||
ClipReader *gif() {
|
||||
return (_gif == BadClipReader) ? nullptr : _gif;
|
||||
Media::Clip::Reader *_gif;
|
||||
Media::Clip::Reader *gif() {
|
||||
return (_gif == Media::Clip::BadReader) ? nullptr : _gif;
|
||||
}
|
||||
const ClipReader *gif() const {
|
||||
return (_gif == BadClipReader) ? nullptr : _gif;
|
||||
const Media::Clip::Reader *gif() const {
|
||||
return (_gif == Media::Clip::BadReader) ? nullptr : _gif;
|
||||
}
|
||||
|
||||
void setStatusSize(int32 newSize) const;
|
||||
|
@ -2377,7 +2377,7 @@ public:
|
|||
DocumentData *getDocument() override {
|
||||
return _attach ? _attach->getDocument() : 0;
|
||||
}
|
||||
ClipReader *getClipReader() override {
|
||||
Media::Clip::Reader *getClipReader() override {
|
||||
return _attach ? _attach->getClipReader() : 0;
|
||||
}
|
||||
bool playInline(bool autoplay) override {
|
||||
|
@ -2445,8 +2445,6 @@ struct LocationData;
|
|||
class LocationManager : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
LocationManager() : manager(0), black(0) {
|
||||
}
|
||||
void init();
|
||||
void reinit();
|
||||
void deinit();
|
||||
|
@ -2464,10 +2462,10 @@ public slots:
|
|||
private:
|
||||
void failed(LocationData *data);
|
||||
|
||||
QNetworkAccessManager *manager;
|
||||
QNetworkAccessManager *manager = nullptr;
|
||||
QMap<QNetworkReply*, LocationData*> dataLoadings, imageLoadings;
|
||||
QMap<LocationData*, int32> serverRedirects;
|
||||
ImagePtr *black;
|
||||
ImagePtr *black = nullptr;
|
||||
};
|
||||
|
||||
class HistoryLocation : public HistoryMedia {
|
||||
|
|
|
@ -53,7 +53,7 @@ FieldAutocomplete::FieldAutocomplete(QWidget *parent) : TWidget(parent)
|
|||
_inner->setGeometry(rect());
|
||||
_scroll->setGeometry(rect());
|
||||
|
||||
_scroll->setWidget(_inner);
|
||||
_scroll->setOwnedWidget(_inner);
|
||||
_scroll->show();
|
||||
_inner->show();
|
||||
|
||||
|
@ -153,15 +153,15 @@ void FieldAutocomplete::updateFiltered(bool resetScroll) {
|
|||
StickerPack srows;
|
||||
if (_emoji) {
|
||||
QMap<uint64, uint64> setsToRequest;
|
||||
Stickers::Sets &sets(Global::RefStickerSets());
|
||||
const Stickers::Order &order(Global::StickerSetsOrder());
|
||||
auto &sets = Global::RefStickerSets();
|
||||
auto &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)) {
|
||||
} else if (!(it->flags & MTPDstickerSet::Flag::f_archived)) {
|
||||
StickersByEmojiMap::const_iterator i = it->emoji.constFind(emojiGetNoColor(_emoji));
|
||||
if (i != it->emoji.cend()) {
|
||||
srows += *i;
|
||||
|
@ -196,14 +196,14 @@ void FieldAutocomplete::updateFiltered(bool resetScroll) {
|
|||
}
|
||||
return true;
|
||||
};
|
||||
auto filterNotPassedByName = [this](UserData *user) -> bool {
|
||||
auto filterNotPassedByName = [this, &filterNotPassedByUsername](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;
|
||||
return filterNotPassedByUsername(user);
|
||||
};
|
||||
|
||||
bool listAllSuggestions = _filter.isEmpty();
|
||||
|
@ -946,4 +946,7 @@ void FieldAutocompleteInner::onPreview() {
|
|||
}
|
||||
}
|
||||
|
||||
FieldAutocompleteInner::~FieldAutocompleteInner() {
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
|
|
|
@ -37,7 +37,6 @@ class FieldAutocomplete final : public TWidget {
|
|||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
FieldAutocomplete(QWidget *parent);
|
||||
|
||||
void fastHide();
|
||||
|
@ -79,7 +78,6 @@ public:
|
|||
~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;
|
||||
|
@ -88,14 +86,12 @@ signals:
|
|||
void moderateKeyActivate(int key, bool *outHandled) const;
|
||||
|
||||
public slots:
|
||||
|
||||
void hideStart();
|
||||
void hideFinish();
|
||||
|
||||
void showStart();
|
||||
|
||||
private:
|
||||
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
|
||||
void updateFiltered(bool resetScroll = false);
|
||||
|
@ -146,7 +142,6 @@ class FieldAutocompleteInner final : public TWidget {
|
|||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
FieldAutocompleteInner(FieldAutocomplete *parent, MentionRows *mrows, HashtagRows *hrows, BotCommandRows *brows, StickerPack *srows);
|
||||
|
||||
void clearSel(bool hidden = false);
|
||||
|
@ -155,8 +150,9 @@ public:
|
|||
|
||||
void setRecentInlineBotsInRows(int32 bots);
|
||||
|
||||
signals:
|
||||
~FieldAutocompleteInner();
|
||||
|
||||
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;
|
||||
|
@ -164,13 +160,11 @@ signals:
|
|||
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;
|
||||
|
||||
|
@ -199,6 +193,7 @@ private:
|
|||
bool _previewShown;
|
||||
|
||||
QTimer _previewTimer;
|
||||
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
|
|
|
@ -29,6 +29,8 @@ historyToDownArrow: icon {
|
|||
{ "history_down_arrow", #b9b9b9, point(14px, 19px) },
|
||||
};
|
||||
historyToDownPaddingTop: 10px;
|
||||
historyToDownBadgeFont: semiboldFont;
|
||||
historyToDownBadgeSize: 22px;
|
||||
|
||||
membersInnerScroll: flatScroll(solidScroll) {
|
||||
deltat: 3px;
|
||||
|
@ -43,8 +45,6 @@ membersInnerDropdown: InnerDropdown(defaultInnerDropdown) {
|
|||
scrollMargin: margins(0px, 5px, 0px, 5px);
|
||||
scrollPadding: margins(0px, 3px, 8px, 3px);
|
||||
}
|
||||
historyToDownBadgeFont: semiboldFont;
|
||||
historyToDownBadgeSize: 22px;
|
||||
|
||||
historyServiceMsgRadius: 12px;
|
||||
historyServiceMsgInvertedRadius: 6px;
|
||||
|
|
|
@ -33,6 +33,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
#include "data/data_drafts.h"
|
||||
#include "history/history_service_layout.h"
|
||||
#include "profile/profile_members_widget.h"
|
||||
#include "core/click_handler_types.h"
|
||||
#include "lang.h"
|
||||
#include "application.h"
|
||||
#include "mainwidget.h"
|
||||
|
@ -40,7 +41,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
#include "passcodewidget.h"
|
||||
#include "mainwindow.h"
|
||||
#include "fileuploader.h"
|
||||
#include "audio.h"
|
||||
#include "media/media_audio.h"
|
||||
#include "localstorage.h"
|
||||
#include "apiwrap.h"
|
||||
#include "window/top_bar_widget.h"
|
||||
|
@ -1481,10 +1482,6 @@ void HistoryInner::keyPressEvent(QKeyEvent *e) {
|
|||
if (!_selected.isEmpty() && selectedForDelete == selectedForForward) {
|
||||
_widget->onDeleteSelected();
|
||||
}
|
||||
} else if (e->key() == Qt::Key_Return || e->key() == Qt::Key_Enter) {
|
||||
if (_selected.isEmpty()) {
|
||||
_widget->onListEnterPressed();
|
||||
}
|
||||
} else {
|
||||
e->ignore();
|
||||
}
|
||||
|
@ -2143,7 +2140,7 @@ int HistoryInner::itemTop(const HistoryItem *item) const { // -1 if should not b
|
|||
}
|
||||
|
||||
void HistoryInner::notifyIsBotChanged() {
|
||||
BotInfo *newinfo = (_history && _history->peer->isUser()) ? _history->peer->asUser()->botInfo : nullptr;
|
||||
BotInfo *newinfo = (_history && _history->peer->isUser()) ? _history->peer->asUser()->botInfo.get() : nullptr;
|
||||
if ((!newinfo && !_botAbout) || (newinfo && _botAbout && _botAbout->info == newinfo)) {
|
||||
return;
|
||||
}
|
||||
|
@ -3497,7 +3494,7 @@ void HistoryWidget::onRecordError() {
|
|||
}
|
||||
|
||||
void HistoryWidget::onRecordDone(QByteArray result, VoiceWaveform waveform, qint32 samples) {
|
||||
if (!_peer) return;
|
||||
if (!_peer || result.isEmpty()) return;
|
||||
|
||||
App::wnd()->activateWindow();
|
||||
int32 duration = samples / AudioVoiceMsgFrequency;
|
||||
|
@ -3523,12 +3520,23 @@ void HistoryWidget::onRecordUpdate(quint16 level, qint32 samples) {
|
|||
}
|
||||
|
||||
void HistoryWidget::updateStickers() {
|
||||
if (!Global::LastStickersUpdate() || getms(true) >= Global::LastStickersUpdate() + StickersUpdateTimeout) {
|
||||
auto now = getms(true);
|
||||
if (!Global::LastStickersUpdate() || now >= Global::LastStickersUpdate() + StickersUpdateTimeout) {
|
||||
if (!_stickersUpdateRequest) {
|
||||
_stickersUpdateRequest = MTP::send(MTPmessages_GetAllStickers(MTP_int(Local::countStickersHash(true))), rpcDone(&HistoryWidget::stickersGot), rpcFail(&HistoryWidget::stickersFailed));
|
||||
}
|
||||
}
|
||||
if (!cLastSavedGifsUpdate() || getms(true) >= cLastSavedGifsUpdate() + StickersUpdateTimeout) {
|
||||
if (!Global::LastRecentStickersUpdate() || now >= Global::LastRecentStickersUpdate() + StickersUpdateTimeout) {
|
||||
if (!_recentStickersUpdateRequest) {
|
||||
_recentStickersUpdateRequest = MTP::send(MTPmessages_GetRecentStickers(MTP_int(Local::countRecentStickersHash())), rpcDone(&HistoryWidget::recentStickersGot), rpcFail(&HistoryWidget::recentStickersFailed));
|
||||
}
|
||||
}
|
||||
if (!Global::LastFeaturedStickersUpdate() || now >= Global::LastFeaturedStickersUpdate() + StickersUpdateTimeout) {
|
||||
if (!_featuredStickersUpdateRequest) {
|
||||
_featuredStickersUpdateRequest = MTP::send(MTPmessages_GetFeaturedStickers(MTP_int(Local::countFeaturedStickersHash())), rpcDone(&HistoryWidget::featuredStickersGot), rpcFail(&HistoryWidget::featuredStickersFailed));
|
||||
}
|
||||
}
|
||||
if (!cLastSavedGifsUpdate() || now >= cLastSavedGifsUpdate() + StickersUpdateTimeout) {
|
||||
if (!_savedGifsUpdateRequest) {
|
||||
_savedGifsUpdateRequest = MTP::send(MTPmessages_GetSavedGifs(MTP_int(Local::countSavedGifsHash())), rpcDone(&HistoryWidget::savedGifsGot), rpcFail(&HistoryWidget::savedGifsFailed));
|
||||
}
|
||||
|
@ -3616,24 +3624,33 @@ void HistoryWidget::notify_clipStopperHidden(ClipStopperType type) {
|
|||
if (_list) _list->update();
|
||||
}
|
||||
|
||||
void HistoryWidget::cmd_search() {
|
||||
if (!inFocusChain() || !_peer) return;
|
||||
bool HistoryWidget::cmd_search() {
|
||||
if (!inFocusChain() || !_peer) return false;
|
||||
|
||||
App::main()->searchInPeer(_peer);
|
||||
return true;
|
||||
}
|
||||
|
||||
void HistoryWidget::cmd_next_chat() {
|
||||
bool HistoryWidget::cmd_next_chat() {
|
||||
PeerData *p = 0;
|
||||
MsgId m = 0;
|
||||
App::main()->peerAfter(_peer, qMax(_showAtMsgId, 0), p, m);
|
||||
if (p) Ui::showPeerHistory(p, m);
|
||||
if (p) {
|
||||
Ui::showPeerHistory(p, m);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void HistoryWidget::cmd_previous_chat() {
|
||||
bool HistoryWidget::cmd_previous_chat() {
|
||||
PeerData *p = 0;
|
||||
MsgId m = 0;
|
||||
App::main()->peerBefore(_peer, qMax(_showAtMsgId, 0), p, m);
|
||||
if (p) Ui::showPeerHistory(p, m);
|
||||
if (p) {
|
||||
Ui::showPeerHistory(p, m);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void HistoryWidget::stickersGot(const MTPmessages_AllStickers &stickers) {
|
||||
|
@ -3645,36 +3662,23 @@ void HistoryWidget::stickersGot(const MTPmessages_AllStickers &stickers) {
|
|||
|
||||
const auto &d_sets(d.vsets.c_vector().v);
|
||||
|
||||
Stickers::Order &setsOrder(Global::RefStickerSetsOrder());
|
||||
auto &setsOrder = Global::RefStickerSetsOrder();
|
||||
setsOrder.clear();
|
||||
|
||||
Stickers::Sets &sets(Global::RefStickerSets());
|
||||
auto &sets = Global::RefStickerSets();
|
||||
QMap<uint64, uint64> setsToRequest;
|
||||
for (auto i = sets.begin(), e = sets.end(); i != e; ++i) {
|
||||
i->access = 0; // mark for removing
|
||||
for (auto &set : sets) {
|
||||
if (!(set.flags & MTPDstickerSet::Flag::f_archived)) {
|
||||
set.flags &= ~MTPDstickerSet::Flag::f_installed; // mark for removing
|
||||
}
|
||||
}
|
||||
for (int i = 0, l = d_sets.size(); i != l; ++i) {
|
||||
if (d_sets.at(i).type() == mtpc_stickerSet) {
|
||||
const auto &set(d_sets.at(i).c_stickerSet());
|
||||
auto it = sets.find(set.vid.v);
|
||||
QString title = stickerSetTitle(set);
|
||||
if (it == sets.cend()) {
|
||||
it = sets.insert(set.vid.v, Stickers::Set(set.vid.v, set.vaccess_hash.v, title, qs(set.vshort_name), set.vcount.v, set.vhash.v, set.vflags.v | MTPDstickerSet_ClientFlag::f_not_loaded));
|
||||
} else {
|
||||
it->access = set.vaccess_hash.v;
|
||||
it->title = title;
|
||||
it->shortName = qs(set.vshort_name);
|
||||
it->flags = set.vflags.v;
|
||||
if (it->count != set.vcount.v || it->hash != set.vhash.v || it->emoji.isEmpty()) {
|
||||
it->count = set.vcount.v;
|
||||
it->hash = set.vhash.v;
|
||||
it->flags |= MTPDstickerSet_ClientFlag::f_not_loaded; // need to request this set
|
||||
}
|
||||
}
|
||||
if (!(it->flags & MTPDstickerSet::Flag::f_disabled) || (it->flags & MTPDstickerSet::Flag::f_official)) {
|
||||
setsOrder.push_back(set.vid.v);
|
||||
if (it->stickers.isEmpty() || (it->flags & MTPDstickerSet_ClientFlag::f_not_loaded)) {
|
||||
setsToRequest.insert(set.vid.v, set.vaccess_hash.v);
|
||||
for_const (auto &setData, d_sets) {
|
||||
if (setData.type() == mtpc_stickerSet) {
|
||||
auto set = Stickers::feedSet(setData.c_stickerSet());
|
||||
if (!(set->flags & MTPDstickerSet::Flag::f_archived) || (set->flags & MTPDstickerSet::Flag::f_official)) {
|
||||
setsOrder.push_back(set->id);
|
||||
if (set->stickers.isEmpty() || (set->flags & MTPDstickerSet_ClientFlag::f_not_loaded)) {
|
||||
setsToRequest.insert(set->id, set->access);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3682,9 +3686,11 @@ void HistoryWidget::stickersGot(const MTPmessages_AllStickers &stickers) {
|
|||
bool writeRecent = false;
|
||||
RecentStickerPack &recent(cGetRecentStickers());
|
||||
for (Stickers::Sets::iterator it = sets.begin(), e = sets.end(); it != e;) {
|
||||
if (it->id == Stickers::CustomSetId || it->access != 0) {
|
||||
++it;
|
||||
} else {
|
||||
bool installed = (it->flags & MTPDstickerSet::Flag::f_installed);
|
||||
bool featured = (it->flags & MTPDstickerSet_ClientFlag::f_featured);
|
||||
bool special = (it->flags & MTPDstickerSet_ClientFlag::f_special);
|
||||
bool archived = (it->flags & MTPDstickerSet::Flag::f_archived);
|
||||
if (!installed) { // remove not mine sets from recent stickers
|
||||
for (RecentStickerPack::iterator i = recent.begin(); i != recent.cend();) {
|
||||
if (it->stickers.indexOf(i->first) >= 0) {
|
||||
i = recent.erase(i);
|
||||
|
@ -3693,6 +3699,10 @@ void HistoryWidget::stickersGot(const MTPmessages_AllStickers &stickers) {
|
|||
++i;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (installed || featured || special || archived) {
|
||||
++it;
|
||||
} else {
|
||||
it = sets.erase(it);
|
||||
}
|
||||
}
|
||||
|
@ -3708,7 +3718,7 @@ void HistoryWidget::stickersGot(const MTPmessages_AllStickers &stickers) {
|
|||
App::api()->requestStickerSets();
|
||||
}
|
||||
|
||||
Local::writeStickers();
|
||||
Local::writeInstalledStickers();
|
||||
if (writeRecent) Local::writeUserSettings();
|
||||
|
||||
if (App::main()) emit App::main()->stickersUpdated();
|
||||
|
@ -3724,6 +3734,193 @@ bool HistoryWidget::stickersFailed(const RPCError &error) {
|
|||
return true;
|
||||
}
|
||||
|
||||
void HistoryWidget::recentStickersGot(const MTPmessages_RecentStickers &stickers) {
|
||||
Global::SetLastRecentStickersUpdate(getms(true));
|
||||
_recentStickersUpdateRequest = 0;
|
||||
|
||||
if (stickers.type() != mtpc_messages_recentStickers) return;
|
||||
auto &d = stickers.c_messages_recentStickers();
|
||||
|
||||
auto &sets = Global::RefStickerSets();
|
||||
auto it = sets.find(Stickers::CloudRecentSetId);
|
||||
|
||||
auto &d_docs = d.vstickers.c_vector().v;
|
||||
if (d_docs.isEmpty()) {
|
||||
if (it != sets.cend()) {
|
||||
sets.erase(it);
|
||||
}
|
||||
} else {
|
||||
if (it == sets.cend()) {
|
||||
it = sets.insert(Stickers::CloudRecentSetId, Stickers::Set(Stickers::CloudRecentSetId, 0, lang(lng_recent_stickers), QString(), 0, 0, qFlags(MTPDstickerSet_ClientFlag::f_special)));
|
||||
} else {
|
||||
it->title = lang(lng_recent_stickers);
|
||||
}
|
||||
it->hash = d.vhash.v;
|
||||
|
||||
auto custom = sets.find(Stickers::CustomSetId);
|
||||
|
||||
StickerPack pack;
|
||||
pack.reserve(d_docs.size());
|
||||
for (int32 i = 0, l = d_docs.size(); i != l; ++i) {
|
||||
DocumentData *doc = App::feedDocument(d_docs.at(i));
|
||||
if (!doc || !doc->sticker()) continue;
|
||||
|
||||
pack.push_back(doc);
|
||||
if (custom != sets.cend()) {
|
||||
int32 index = custom->stickers.indexOf(doc);
|
||||
if (index >= 0) {
|
||||
custom->stickers.removeAt(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (custom != sets.cend() && custom->stickers.isEmpty()) {
|
||||
sets.erase(custom);
|
||||
custom = sets.end();
|
||||
}
|
||||
|
||||
bool writeRecent = false;
|
||||
RecentStickerPack &recent(cGetRecentStickers());
|
||||
for (RecentStickerPack::iterator i = recent.begin(); i != recent.cend();) {
|
||||
if (it->stickers.indexOf(i->first) >= 0 && pack.indexOf(i->first) < 0) {
|
||||
i = recent.erase(i);
|
||||
writeRecent = true;
|
||||
} else {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
if (pack.isEmpty()) {
|
||||
sets.erase(it);
|
||||
} else {
|
||||
it->stickers = pack;
|
||||
it->emoji.clear();
|
||||
}
|
||||
|
||||
if (writeRecent) {
|
||||
Local::writeUserSettings();
|
||||
}
|
||||
}
|
||||
|
||||
if (Local::countRecentStickersHash() != d.vhash.v) {
|
||||
LOG(("API Error: received stickers hash %1 while counted hash is %2").arg(d.vhash.v).arg(Local::countRecentStickersHash()));
|
||||
}
|
||||
|
||||
Local::writeRecentStickers();
|
||||
|
||||
if (App::main()) emit App::main()->stickersUpdated();
|
||||
}
|
||||
|
||||
bool HistoryWidget::recentStickersFailed(const RPCError &error) {
|
||||
if (MTP::isDefaultHandledError(error)) return false;
|
||||
|
||||
LOG(("App Fail: Failed to get recent stickers!"));
|
||||
|
||||
Global::SetLastRecentStickersUpdate(getms(true));
|
||||
_recentStickersUpdateRequest = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
void HistoryWidget::featuredStickersGot(const MTPmessages_FeaturedStickers &stickers) {
|
||||
Global::SetLastFeaturedStickersUpdate(getms(true));
|
||||
_featuredStickersUpdateRequest = 0;
|
||||
|
||||
if (stickers.type() != mtpc_messages_featuredStickers) return;
|
||||
auto &d(stickers.c_messages_featuredStickers());
|
||||
|
||||
OrderedSet<uint64> unread;
|
||||
for_const (auto &unreadSetId, d.vunread.c_vector().v) {
|
||||
unread.insert(unreadSetId.v);
|
||||
}
|
||||
|
||||
auto &d_sets(d.vsets.c_vector().v);
|
||||
|
||||
auto &setsOrder = Global::RefFeaturedStickerSetsOrder();
|
||||
setsOrder.clear();
|
||||
|
||||
auto &sets = Global::RefStickerSets();
|
||||
QMap<uint64, uint64> setsToRequest;
|
||||
for (auto &set : sets) {
|
||||
set.flags &= ~MTPDstickerSet_ClientFlag::f_featured; // mark for removing
|
||||
}
|
||||
for (int i = 0, l = d_sets.size(); i != l; ++i) {
|
||||
if (d_sets.at(i).type() == mtpc_stickerSetCovered && d_sets.at(i).c_stickerSetCovered().vset.type() == mtpc_stickerSet) {
|
||||
const auto &set(d_sets.at(i).c_stickerSetCovered().vset.c_stickerSet());
|
||||
auto it = sets.find(set.vid.v);
|
||||
QString title = stickerSetTitle(set);
|
||||
if (it == sets.cend()) {
|
||||
auto setClientFlags = MTPDstickerSet_ClientFlag::f_featured | MTPDstickerSet_ClientFlag::f_not_loaded;
|
||||
if (unread.contains(set.vid.v)) {
|
||||
setClientFlags |= MTPDstickerSet_ClientFlag::f_unread;
|
||||
}
|
||||
it = sets.insert(set.vid.v, Stickers::Set(set.vid.v, set.vaccess_hash.v, title, qs(set.vshort_name), set.vcount.v, set.vhash.v, set.vflags.v | setClientFlags));
|
||||
} else {
|
||||
it->access = set.vaccess_hash.v;
|
||||
it->title = title;
|
||||
it->shortName = qs(set.vshort_name);
|
||||
auto clientFlags = it->flags & (MTPDstickerSet_ClientFlag::f_featured | MTPDstickerSet_ClientFlag::f_unread | MTPDstickerSet_ClientFlag::f_not_loaded | MTPDstickerSet_ClientFlag::f_special);
|
||||
it->flags = set.vflags.v | clientFlags;
|
||||
it->flags |= MTPDstickerSet_ClientFlag::f_featured;
|
||||
if (unread.contains(it->id)) {
|
||||
it->flags |= MTPDstickerSet_ClientFlag::f_unread;
|
||||
} else {
|
||||
it->flags &= ~MTPDstickerSet_ClientFlag::f_unread;
|
||||
}
|
||||
if (it->count != set.vcount.v || it->hash != set.vhash.v || it->emoji.isEmpty()) {
|
||||
it->count = set.vcount.v;
|
||||
it->hash = set.vhash.v;
|
||||
it->flags |= MTPDstickerSet_ClientFlag::f_not_loaded; // need to request this set
|
||||
}
|
||||
}
|
||||
setsOrder.push_back(set.vid.v);
|
||||
if (it->stickers.isEmpty() || (it->flags & MTPDstickerSet_ClientFlag::f_not_loaded)) {
|
||||
setsToRequest.insert(set.vid.v, set.vaccess_hash.v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int unreadCount = 0;
|
||||
for (auto it = sets.begin(), e = sets.end(); it != e;) {
|
||||
bool installed = (it->flags & MTPDstickerSet::Flag::f_installed);
|
||||
bool featured = (it->flags & MTPDstickerSet_ClientFlag::f_featured);
|
||||
bool special = (it->flags & MTPDstickerSet_ClientFlag::f_special);
|
||||
bool archived = (it->flags & MTPDstickerSet::Flag::f_archived);
|
||||
if (installed || featured || special || archived) {
|
||||
if (featured && (it->flags & MTPDstickerSet_ClientFlag::f_unread)) {
|
||||
++unreadCount;
|
||||
}
|
||||
++it;
|
||||
} else {
|
||||
it = sets.erase(it);
|
||||
}
|
||||
}
|
||||
Global::SetFeaturedStickerSetsUnreadCount(unreadCount);
|
||||
|
||||
if (Local::countFeaturedStickersHash() != d.vhash.v) {
|
||||
LOG(("API Error: received featured stickers hash %1 while counted hash is %2").arg(d.vhash.v).arg(Local::countFeaturedStickersHash()));
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
Local::writeFeaturedStickers();
|
||||
|
||||
if (App::main()) emit App::main()->stickersUpdated();
|
||||
}
|
||||
|
||||
bool HistoryWidget::featuredStickersFailed(const RPCError &error) {
|
||||
if (MTP::isDefaultHandledError(error)) return false;
|
||||
|
||||
LOG(("App Fail: Failed to get featured stickers!"));
|
||||
|
||||
Global::SetLastFeaturedStickersUpdate(getms(true));
|
||||
_featuredStickersUpdateRequest = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
void HistoryWidget::savedGifsGot(const MTPmessages_SavedGifs &gifs) {
|
||||
cSetLastSavedGifsUpdate(getms(true));
|
||||
_savedGifsUpdateRequest = 0;
|
||||
|
@ -4566,7 +4763,7 @@ bool HistoryWidget::messagesFailed(const RPCError &error, mtpRequestId requestId
|
|||
|
||||
if (error.type() == qstr("CHANNEL_PRIVATE") || error.type() == qstr("CHANNEL_PUBLIC_GROUP_NA") || error.type() == qstr("USER_BANNED_IN_CHANNEL")) {
|
||||
PeerData *was = _peer;
|
||||
Ui::showChatsList();
|
||||
App::main()->showBackFromStack();
|
||||
Ui::showLayer(new InformBox(lang((was && was->isMegagroup()) ? lng_group_not_accessible : lng_channel_not_accessible)));
|
||||
return true;
|
||||
}
|
||||
|
@ -4578,7 +4775,7 @@ bool HistoryWidget::messagesFailed(const RPCError &error, mtpRequestId requestId
|
|||
_preloadDownRequest = 0;
|
||||
} else if (_firstLoadRequest == requestId) {
|
||||
_firstLoadRequest = 0;
|
||||
Ui::showChatsList();
|
||||
App::main()->showBackFromStack();
|
||||
} else if (_delayedShowAtRequest == requestId) {
|
||||
_delayedShowAtRequest = 0;
|
||||
}
|
||||
|
@ -5596,6 +5793,8 @@ void HistoryWidget::botCallbackDone(BotCallbackInfo info, const MTPmessages_BotC
|
|||
toast.text = qs(answerData.vmessage);
|
||||
Ui::Toast::Show(App::wnd(), toast);
|
||||
}
|
||||
} else if (answerData.has_url()) {
|
||||
UrlClickHandler(qs(answerData.vurl)).onClick(Qt::LeftButton);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5921,8 +6120,9 @@ void HistoryWidget::onKbToggle(bool manual) {
|
|||
} else {
|
||||
if (_history) {
|
||||
_history->clearLastKeyboard();
|
||||
} else {
|
||||
updateBotKeyboard();
|
||||
}
|
||||
updateBotKeyboard();
|
||||
}
|
||||
} else if (!_keyboard.hasMarkup() && _keyboard.forceReply()) {
|
||||
_kbHide.hide();
|
||||
|
@ -6030,7 +6230,7 @@ void HistoryWidget::paintTopBar(Painter &p, float64 over, int32 decreaseWidth) {
|
|||
|
||||
if (!_history) return;
|
||||
|
||||
int32 increaseLeft = Adaptive::OneColumn() ? (st::topBarForwardPadding.right() - st::topBarForwardPadding.left()) : 0;
|
||||
int32 increaseLeft = (Adaptive::OneColumn() || !App::main()->stackIsEmpty()) ? (st::topBarForwardPadding.right() - st::topBarForwardPadding.left()) : 0;
|
||||
decreaseWidth += increaseLeft;
|
||||
QRect rectForName(st::topBarForwardPadding.left() + increaseLeft, st::topBarForwardPadding.top(), width() - decreaseWidth - st::topBarForwardPadding.left() - st::topBarForwardPadding.right(), st::msgNameFont->height);
|
||||
p.setFont(st::dialogsTextFont);
|
||||
|
@ -6045,7 +6245,7 @@ void HistoryWidget::paintTopBar(Painter &p, float64 over, int32 decreaseWidth) {
|
|||
p.setPen(st::dialogsNameFg);
|
||||
_peer->dialogName().drawElided(p, rectForName.left(), rectForName.top(), rectForName.width());
|
||||
|
||||
if (Adaptive::OneColumn()) {
|
||||
if (Adaptive::OneColumn() || !App::main()->stackIsEmpty()) {
|
||||
p.setOpacity(st::topBarForwardAlpha + (1 - st::topBarForwardAlpha) * over);
|
||||
p.drawSprite(QPoint((st::topBarForwardPadding.right() - st::topBarBackwardImg.pxWidth()) / 2, (st::topBarHeight - st::topBarBackwardImg.pxHeight()) / 2), st::topBarBackwardImg);
|
||||
} else {
|
||||
|
@ -6055,7 +6255,7 @@ void HistoryWidget::paintTopBar(Painter &p, float64 over, int32 decreaseWidth) {
|
|||
}
|
||||
|
||||
QRect HistoryWidget::getMembersShowAreaGeometry() const {
|
||||
int increaseLeft = Adaptive::OneColumn() ? (st::topBarForwardPadding.right() - st::topBarForwardPadding.left()) : 0;
|
||||
int increaseLeft = (Adaptive::OneColumn() || !App::main()->stackIsEmpty()) ? (st::topBarForwardPadding.right() - st::topBarForwardPadding.left()) : 0;
|
||||
int membersTextLeft = st::topBarForwardPadding.left() + increaseLeft;
|
||||
int membersTextTop = st::topBarHeight - st::topBarForwardPadding.bottom() - st::dialogsTextFont->height;
|
||||
int membersTextWidth = _titlePeerTextWidth;
|
||||
|
@ -6101,8 +6301,8 @@ void HistoryWidget::onMembersDropdownHidden() {
|
|||
}
|
||||
|
||||
void HistoryWidget::topBarClick() {
|
||||
if (Adaptive::OneColumn()) {
|
||||
Ui::showChatsList();
|
||||
if (Adaptive::OneColumn() || !App::main()->stackIsEmpty()) {
|
||||
App::main()->showBackFromStack();
|
||||
} else {
|
||||
if (_history) Ui::showPeerProfile(_peer);
|
||||
}
|
||||
|
@ -6654,10 +6854,10 @@ void HistoryWidget::onReportSpamClear() {
|
|||
if (_clearPeer->isUser()) {
|
||||
App::main()->deleteConversation(_clearPeer);
|
||||
} else if (_clearPeer->isChat()) {
|
||||
Ui::showChatsList();
|
||||
App::main()->showBackFromStack();
|
||||
MTP::send(MTPmessages_DeleteChatUser(_clearPeer->asChat()->inputChat, App::self()->inputUser), App::main()->rpcDone(&MainWidget::deleteHistoryAfterLeave, _clearPeer), App::main()->rpcFail(&MainWidget::leaveChatFailed, _clearPeer));
|
||||
} else if (_clearPeer->isChannel()) {
|
||||
Ui::showChatsList();
|
||||
App::main()->showBackFromStack();
|
||||
if (_clearPeer->migrateFrom()) {
|
||||
App::main()->deleteConversation(_clearPeer->migrateFrom());
|
||||
}
|
||||
|
@ -7185,7 +7385,7 @@ void HistoryWidget::keyPressEvent(QKeyEvent *e) {
|
|||
if (e->key() == Qt::Key_Escape) {
|
||||
e->ignore();
|
||||
} else if (e->key() == Qt::Key_Back) {
|
||||
Ui::showChatsList();
|
||||
App::main()->showBackFromStack();
|
||||
emit cancelled();
|
||||
} else if (e->key() == Qt::Key_PageDown) {
|
||||
_scroll.keyPressEvent(e);
|
||||
|
@ -7206,6 +7406,8 @@ void HistoryWidget::keyPressEvent(QKeyEvent *e) {
|
|||
}
|
||||
_scroll.keyPressEvent(e);
|
||||
}
|
||||
} else if (e->key() == Qt::Key_Return || e->key() == Qt::Key_Enter) {
|
||||
onListEnterPressed();
|
||||
} else {
|
||||
e->ignore();
|
||||
}
|
||||
|
@ -7963,7 +8165,7 @@ void HistoryWidget::onCancel() {
|
|||
} else if (!_fieldAutocomplete->isHidden()) {
|
||||
_fieldAutocomplete->hideStart();
|
||||
} else {
|
||||
Ui::showChatsList();
|
||||
App::main()->showBackFromStack();
|
||||
emit cancelled();
|
||||
}
|
||||
}
|
||||
|
@ -8001,7 +8203,7 @@ void HistoryWidget::peerUpdated(PeerData *data) {
|
|||
}
|
||||
QString restriction = _peer->restrictionReason();
|
||||
if (!restriction.isEmpty()) {
|
||||
Ui::showChatsList();
|
||||
App::main()->showBackFromStack();
|
||||
Ui::showLayer(new InformBox(restriction));
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -726,9 +726,9 @@ public:
|
|||
void notify_clipStopperHidden(ClipStopperType type);
|
||||
void notify_handlePendingHistoryUpdate();
|
||||
|
||||
void cmd_search();
|
||||
void cmd_next_chat();
|
||||
void cmd_previous_chat();
|
||||
bool cmd_search();
|
||||
bool cmd_next_chat();
|
||||
bool cmd_previous_chat();
|
||||
|
||||
~HistoryWidget();
|
||||
|
||||
|
@ -1005,6 +1005,14 @@ private:
|
|||
void stickersGot(const MTPmessages_AllStickers &stickers);
|
||||
bool stickersFailed(const RPCError &error);
|
||||
|
||||
mtpRequestId _recentStickersUpdateRequest = 0;
|
||||
void recentStickersGot(const MTPmessages_RecentStickers &stickers);
|
||||
bool recentStickersFailed(const RPCError &error);
|
||||
|
||||
mtpRequestId _featuredStickersUpdateRequest = 0;
|
||||
void featuredStickersGot(const MTPmessages_FeaturedStickers &stickers);
|
||||
bool featuredStickersFailed(const RPCError &error);
|
||||
|
||||
mtpRequestId _savedGifsUpdateRequest = 0;
|
||||
void savedGifsGot(const MTPmessages_SavedGifs &gifs);
|
||||
bool savedGifsFailed(const RPCError &error);
|
||||
|
|
|
@ -23,6 +23,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
|
||||
#include "styles/style_overview.h"
|
||||
#include "inline_bots/inline_bot_result.h"
|
||||
#include "media/media_clip_reader.h"
|
||||
#include "localstorage.h"
|
||||
#include "mainwidget.h"
|
||||
#include "lang.h"
|
||||
|
@ -131,9 +132,9 @@ void Gif::paint(Painter &p, const QRect &clip, const PaintContext *context) cons
|
|||
document->automaticLoad(nullptr);
|
||||
|
||||
bool loaded = document->loaded(), loading = document->loading(), displayLoading = document->displayLoading();
|
||||
if (loaded && !gif() && _gif != BadClipReader) {
|
||||
if (loaded && !gif() && _gif != Media::Clip::BadReader) {
|
||||
Gif *that = const_cast<Gif*>(this);
|
||||
that->_gif = new ClipReader(document->location(), document->data(), func(that, &Gif::clipCallback));
|
||||
that->_gif = new Media::Clip::Reader(document->location(), document->data(), func(that, &Gif::clipCallback));
|
||||
if (gif()) _gif->setAutoplay();
|
||||
}
|
||||
|
||||
|
@ -162,7 +163,7 @@ void Gif::paint(Painter &p, const QRect &clip, const PaintContext *context) cons
|
|||
}
|
||||
}
|
||||
|
||||
if (radial || (!_gif && !loaded && !loading) || (_gif == BadClipReader)) {
|
||||
if (radial || (!_gif && !loaded && !loading) || (_gif == Media::Clip::BadReader)) {
|
||||
float64 radialOpacity = (radial && loaded) ? _animation->radial.opacity() : 1;
|
||||
if (_animation && _animation->_a_over.animating(context->ms)) {
|
||||
float64 over = _animation->_a_over.current();
|
||||
|
@ -326,19 +327,20 @@ void Gif::step_radial(uint64 ms, bool timer) {
|
|||
}
|
||||
}
|
||||
|
||||
void Gif::clipCallback(ClipReaderNotification notification) {
|
||||
void Gif::clipCallback(Media::Clip::Notification notification) {
|
||||
using namespace Media::Clip;
|
||||
switch (notification) {
|
||||
case ClipReaderReinit: {
|
||||
case NotificationReinit: {
|
||||
if (gif()) {
|
||||
if (_gif->state() == ClipError) {
|
||||
if (_gif->state() == State::Error) {
|
||||
delete _gif;
|
||||
_gif = BadClipReader;
|
||||
_gif = BadReader;
|
||||
getShownDocument()->forget();
|
||||
} else if (_gif->ready() && !_gif->started()) {
|
||||
int32 height = st::inlineMediaHeight;
|
||||
QSize frame = countFrameSize();
|
||||
_gif->start(frame.width(), frame.height(), _width, height, false);
|
||||
} else if (_gif->paused() && !Ui::isInlineItemVisible(this)) {
|
||||
_gif->start(frame.width(), frame.height(), _width, height, ImageRoundRadius::None);
|
||||
} else if (_gif->autoPausedGif() && !Ui::isInlineItemVisible(this)) {
|
||||
delete _gif;
|
||||
_gif = nullptr;
|
||||
getShownDocument()->forget();
|
||||
|
@ -348,7 +350,7 @@ void Gif::clipCallback(ClipReaderNotification notification) {
|
|||
update();
|
||||
} break;
|
||||
|
||||
case ClipReaderRepaint: {
|
||||
case NotificationRepaint: {
|
||||
if (gif() && !_gif->currentDisplayed()) {
|
||||
update();
|
||||
}
|
||||
|
@ -852,39 +854,29 @@ bool File::updateStatusText() const {
|
|||
statusSize = document->loadOffset();
|
||||
} else if (document->loaded()) {
|
||||
if (document->voice()) {
|
||||
AudioMsgId playing;
|
||||
AudioPlayerState playingState = AudioPlayerStopped;
|
||||
int64 playingPosition = 0, playingDuration = 0;
|
||||
int32 playingFrequency = 0;
|
||||
statusSize = FileStatusSizeLoaded;
|
||||
if (audioPlayer()) {
|
||||
audioPlayer()->currentState(&playing, &playingState, &playingPosition, &playingDuration, &playingFrequency);
|
||||
}
|
||||
|
||||
if (playing == AudioMsgId(document, FullMsgId()) && !(playingState & AudioPlayerStoppedMask) && playingState != AudioPlayerFinishing) {
|
||||
statusSize = -1 - (playingPosition / (playingFrequency ? playingFrequency : AudioVoiceMsgFrequency));
|
||||
realDuration = playingDuration / (playingFrequency ? playingFrequency : AudioVoiceMsgFrequency);
|
||||
showPause = (playingState == AudioPlayerPlaying || playingState == AudioPlayerResuming || playingState == AudioPlayerStarting);
|
||||
} else {
|
||||
statusSize = FileStatusSizeLoaded;
|
||||
AudioMsgId playing;
|
||||
auto playbackState = audioPlayer()->currentState(&playing, AudioMsgId::Type::Voice);
|
||||
if (playing == AudioMsgId(document, FullMsgId()) && !(playbackState.state & AudioPlayerStoppedMask) && playbackState.state != AudioPlayerFinishing) {
|
||||
statusSize = -1 - (playbackState.position / (playbackState.frequency ? playbackState.frequency : AudioVoiceMsgFrequency));
|
||||
realDuration = playbackState.duration / (playbackState.frequency ? playbackState.frequency : AudioVoiceMsgFrequency);
|
||||
showPause = (playbackState.state == AudioPlayerPlaying || playbackState.state == AudioPlayerResuming || playbackState.state == AudioPlayerStarting);
|
||||
}
|
||||
}
|
||||
} else if (document->song()) {
|
||||
SongMsgId playing;
|
||||
AudioPlayerState playingState = AudioPlayerStopped;
|
||||
int64 playingPosition = 0, playingDuration = 0;
|
||||
int32 playingFrequency = 0;
|
||||
statusSize = FileStatusSizeLoaded;
|
||||
if (audioPlayer()) {
|
||||
audioPlayer()->currentState(&playing, &playingState, &playingPosition, &playingDuration, &playingFrequency);
|
||||
}
|
||||
|
||||
if (playing == SongMsgId(document, FullMsgId()) && !(playingState & AudioPlayerStoppedMask) && playingState != AudioPlayerFinishing) {
|
||||
statusSize = -1 - (playingPosition / (playingFrequency ? playingFrequency : AudioVoiceMsgFrequency));
|
||||
realDuration = playingDuration / (playingFrequency ? playingFrequency : AudioVoiceMsgFrequency);
|
||||
showPause = (playingState == AudioPlayerPlaying || playingState == AudioPlayerResuming || playingState == AudioPlayerStarting);
|
||||
} else {
|
||||
statusSize = FileStatusSizeLoaded;
|
||||
}
|
||||
if (!showPause && (playing == SongMsgId(document, FullMsgId())) && App::main() && App::main()->player()->seekingSong(playing)) {
|
||||
showPause = true;
|
||||
AudioMsgId playing;
|
||||
auto playbackState = audioPlayer()->currentState(&playing, AudioMsgId::Type::Song);
|
||||
if (playing == AudioMsgId(document, FullMsgId()) && !(playbackState.state & AudioPlayerStoppedMask) && playbackState.state != AudioPlayerFinishing) {
|
||||
statusSize = -1 - (playbackState.position / (playbackState.frequency ? playbackState.frequency : AudioVoiceMsgFrequency));
|
||||
realDuration = playbackState.duration / (playbackState.frequency ? playbackState.frequency : AudioVoiceMsgFrequency);
|
||||
showPause = (playbackState.state == AudioPlayerPlaying || playbackState.state == AudioPlayerResuming || playbackState.state == AudioPlayerStarting);
|
||||
}
|
||||
if (!showPause && (playing == AudioMsgId(document, FullMsgId())) && App::main() && App::main()->player()->seekingSong(playing)) {
|
||||
showPause = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
statusSize = FileStatusSizeLoaded;
|
||||
|
|
|
@ -92,10 +92,10 @@ private:
|
|||
return ~StateFlags(flag);
|
||||
}
|
||||
|
||||
ClipReader *_gif = nullptr;
|
||||
Media::Clip::Reader *_gif = nullptr;
|
||||
ClickHandlerPtr _delete;
|
||||
bool gif() const {
|
||||
return (!_gif || _gif == BadClipReader) ? false : true;
|
||||
return (!_gif || _gif == Media::Clip::BadReader) ? false : true;
|
||||
}
|
||||
mutable QPixmap _thumb;
|
||||
void prepareThumb(int32 width, int32 height, const QSize &frame) const;
|
||||
|
@ -104,7 +104,7 @@ private:
|
|||
bool isRadialAnimation(uint64 ms) const;
|
||||
void step_radial(uint64 ms, bool timer);
|
||||
|
||||
void clipCallback(ClipReaderNotification notification);
|
||||
void clipCallback(Media::Clip::Notification notification);
|
||||
|
||||
struct AnimationData {
|
||||
AnimationData(AnimationCallbacks &&callbacks)
|
||||
|
|
|
@ -352,7 +352,7 @@ void Result::createDocument() {
|
|||
attributes.push_back(MTP_documentAttributeAudio(MTP_flags(flags), MTP_int(_duration), MTPstring(), MTPstring(), MTPbytes()));
|
||||
}
|
||||
|
||||
MTPDocument document = MTP_document(MTP_long(docId), MTP_long(0), MTP_int(unixtime()), MTP_string(mime), MTP_int(0), MTP_photoSizeEmpty(MTP_string("")), MTP_int(MTP::maindc()), MTP_vector<MTPDocumentAttribute>(attributes));
|
||||
MTPDocument document = MTP_document(MTP_long(docId), MTP_long(0), MTP_int(unixtime()), MTP_string(mime), MTP_int(0), MTP_photoSizeEmpty(MTP_string("")), MTP_int(MTP::maindc()), MTP_int(0), MTP_vector<MTPDocumentAttribute>(attributes));
|
||||
|
||||
_document = App::feedDocument(document);
|
||||
_document->setContentUrl(_content_url);
|
||||
|
|
|
@ -29,15 +29,12 @@ class CodeInput final : public FlatInput {
|
|||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
CodeInput(QWidget *parent, const style::flatInput &st, const QString &ph);
|
||||
|
||||
signals:
|
||||
|
||||
void codeEntered();
|
||||
|
||||
protected:
|
||||
|
||||
void correctValue(const QString &was, QString &now);
|
||||
|
||||
};
|
||||
|
|
|
@ -340,7 +340,28 @@ bool IntroPwdCheck::deleteFail(const RPCError &error) {
|
|||
if (MTP::isDefaultHandledError(error)) return false;
|
||||
|
||||
sentRequest = 0;
|
||||
showError(lang(lng_server_error));
|
||||
|
||||
auto type = error.type();
|
||||
if (type.startsWith(qstr("2FA_CONFIRM_WAIT_"))) {
|
||||
int seconds = type.mid(qstr("2FA_CONFIRM_WAIT_").size()).toInt();
|
||||
int days = (seconds + 59) / 86400;
|
||||
int hours = ((seconds + 59) % 86400) / 3600;
|
||||
int minutes = ((seconds + 59) % 3600) / 60;
|
||||
QString when;
|
||||
if (days > 0) {
|
||||
when = lng_signin_reset_in_days(lt_count_days, days, lt_count_hours, hours, lt_count_minutes, minutes);
|
||||
} else if (hours > 0) {
|
||||
when = lng_signin_reset_in_hours(lt_count_hours, hours, lt_count_minutes, minutes);
|
||||
} else {
|
||||
when = lng_signin_reset_in_minutes(lt_count_minutes, minutes);
|
||||
}
|
||||
Ui::showLayer(new InformBox(lng_signin_reset_wait(lt_phone_number, App::formatPhone(intro()->getPhone()), lt_when, when)));
|
||||
} else if (type == qstr("2FA_RECENT_CONFIRM")) {
|
||||
Ui::showLayer(new InformBox(lang(lng_signin_reset_cancelled)));
|
||||
} else {
|
||||
Ui::hideLayer();
|
||||
showError(lang(lng_server_error));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -242,7 +242,7 @@ void IntroSignup::onCheckRequest() {
|
|||
|
||||
void IntroSignup::onPhotoReady(const QImage &img) {
|
||||
_photoBig = img;
|
||||
_photoSmall = QPixmap::fromImage(img.scaled(st::introPhotoSize * cIntRetinaFactor(), st::introPhotoSize * cIntRetinaFactor(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation), Qt::ColorOnly);
|
||||
_photoSmall = App::pixmapFromImageInPlace(img.scaled(st::introPhotoSize * cIntRetinaFactor(), st::introPhotoSize * cIntRetinaFactor(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
|
||||
_photoSmall.setDevicePixelRatio(cRetinaFactor());
|
||||
}
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
#include "stdafx.h"
|
||||
#include "lang.h"
|
||||
|
||||
#include "media/media_clip_reader.h"
|
||||
#include "layerwidget.h"
|
||||
#include "application.h"
|
||||
#include "mainwindow.h"
|
||||
|
@ -346,9 +347,9 @@ QPixmap MediaPreviewWidget::currentImage() const {
|
|||
} else {
|
||||
_document->automaticLoad(nullptr);
|
||||
if (_document->loaded()) {
|
||||
if (!_gif && _gif != BadClipReader) {
|
||||
MediaPreviewWidget *that = const_cast<MediaPreviewWidget*>(this);
|
||||
that->_gif = new ClipReader(_document->location(), _document->data(), func(that, &MediaPreviewWidget::clipCallback));
|
||||
if (!_gif && _gif != Media::Clip::BadReader) {
|
||||
auto that = const_cast<MediaPreviewWidget*>(this);
|
||||
that->_gif = new Media::Clip::Reader(_document->location(), _document->data(), func(that, &MediaPreviewWidget::clipCallback));
|
||||
if (gif()) _gif->setAutoplay();
|
||||
}
|
||||
}
|
||||
|
@ -385,23 +386,24 @@ QPixmap MediaPreviewWidget::currentImage() const {
|
|||
return _cache;
|
||||
}
|
||||
|
||||
void MediaPreviewWidget::clipCallback(ClipReaderNotification notification) {
|
||||
void MediaPreviewWidget::clipCallback(Media::Clip::Notification notification) {
|
||||
using namespace Media::Clip;
|
||||
switch (notification) {
|
||||
case ClipReaderReinit: {
|
||||
if (gif() && _gif->state() == ClipError) {
|
||||
case NotificationReinit: {
|
||||
if (gif() && _gif->state() == State::Error) {
|
||||
delete _gif;
|
||||
_gif = BadClipReader;
|
||||
_gif = BadReader;
|
||||
}
|
||||
|
||||
if (gif() && _gif->ready() && !_gif->started()) {
|
||||
QSize s = currentDimensions();
|
||||
_gif->start(s.width(), s.height(), s.width(), s.height(), false);
|
||||
_gif->start(s.width(), s.height(), s.width(), s.height(), ImageRoundRadius::None);
|
||||
}
|
||||
|
||||
update();
|
||||
} break;
|
||||
|
||||
case ClipReaderRepaint: {
|
||||
case NotificationRepaint: {
|
||||
if (gif() && !_gif->currentDisplayed()) {
|
||||
update();
|
||||
}
|
||||
|
|
|
@ -135,12 +135,12 @@ private:
|
|||
Animation _a_shown;
|
||||
DocumentData *_document = nullptr;
|
||||
PhotoData *_photo = nullptr;
|
||||
ClipReader *_gif = nullptr;
|
||||
Media::Clip::Reader *_gif = nullptr;
|
||||
bool gif() const {
|
||||
return (!_gif || _gif == BadClipReader) ? false : true;
|
||||
return (!_gif || _gif == Media::Clip::BadReader) ? false : true;
|
||||
}
|
||||
|
||||
void clipCallback(ClipReaderNotification notification);
|
||||
void clipCallback(Media::Clip::Notification notification);
|
||||
|
||||
enum CacheStatus {
|
||||
CacheNotLoaded,
|
||||
|
|
|
@ -30,7 +30,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
#include "playerwidget.h"
|
||||
#include "boxes/addcontactbox.h"
|
||||
#include "boxes/confirmbox.h"
|
||||
#include "audio.h"
|
||||
#include "media/media_audio.h"
|
||||
#include "localstorage.h"
|
||||
|
||||
TextParseOptions _textNameOptions = {
|
||||
|
|
|
@ -21,9 +21,10 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
#include "stdafx.h"
|
||||
#include "localimageloader.h"
|
||||
#include "ui/filedialog.h"
|
||||
#include "audio.h"
|
||||
#include "media/media_audio.h"
|
||||
|
||||
#include "boxes/photosendbox.h"
|
||||
#include "media/media_clip_reader.h"
|
||||
#include "mainwidget.h"
|
||||
#include "mainwindow.h"
|
||||
#include "lang.h"
|
||||
|
@ -311,7 +312,7 @@ void FileLoadTask::process() {
|
|||
if (!cover.isNull()) { // cover to thumb
|
||||
int32 cw = cover.width(), ch = cover.height();
|
||||
if (cw < 20 * ch && ch < 20 * cw) {
|
||||
QPixmap full = (cw > 90 || ch > 90) ? QPixmap::fromImage(cover.scaled(90, 90, Qt::KeepAspectRatio, Qt::SmoothTransformation), Qt::ColorOnly) : QPixmap::fromImage(cover, Qt::ColorOnly);
|
||||
QPixmap full = (cw > 90 || ch > 90) ? App::pixmapFromImageInPlace(cover.scaled(90, 90, Qt::KeepAspectRatio, Qt::SmoothTransformation)) : App::pixmapFromImageInPlace(std_::move(cover));
|
||||
{
|
||||
QByteArray thumbFormat = "JPG";
|
||||
int32 thumbQuality = 87;
|
||||
|
@ -330,7 +331,7 @@ void FileLoadTask::process() {
|
|||
}
|
||||
if (filemime == qstr("video/mp4") || filename.endsWith(qstr(".mp4"), Qt::CaseInsensitive) || animated) {
|
||||
QImage cover;
|
||||
MTPDocumentAttribute animatedAttribute = clipReadAnimatedAttributes(_filepath, _content, cover);
|
||||
MTPDocumentAttribute animatedAttribute = Media::Clip::readAttributes(_filepath, _content, cover);
|
||||
if (animatedAttribute.type() == mtpc_documentAttributeVideo) {
|
||||
int32 cw = cover.width(), ch = cover.height();
|
||||
if (cw < 20 * ch && ch < 20 * cw) {
|
||||
|
@ -338,7 +339,7 @@ void FileLoadTask::process() {
|
|||
attributes.push_back(animatedAttribute);
|
||||
gif = true;
|
||||
|
||||
QPixmap full = (cw > 90 || ch > 90) ? QPixmap::fromImage(cover.scaled(90, 90, Qt::KeepAspectRatio, Qt::SmoothTransformation), Qt::ColorOnly) : QPixmap::fromImage(cover, Qt::ColorOnly);
|
||||
QPixmap full = (cw > 90 || ch > 90) ? App::pixmapFromImageInPlace(cover.scaled(90, 90, Qt::KeepAspectRatio, Qt::SmoothTransformation)) : App::pixmapFromImageInPlace(std_::move(cover));
|
||||
{
|
||||
QByteArray thumbFormat = "JPG";
|
||||
int32 thumbQuality = 87;
|
||||
|
@ -368,15 +369,15 @@ void FileLoadTask::process() {
|
|||
if (animated) {
|
||||
attributes.push_back(MTP_documentAttributeAnimated());
|
||||
} else if (_type != PrepareDocument) {
|
||||
QPixmap thumb = (w > 100 || h > 100) ? QPixmap::fromImage(fullimage.scaled(100, 100, Qt::KeepAspectRatio, Qt::SmoothTransformation), Qt::ColorOnly) : QPixmap::fromImage(fullimage);
|
||||
QPixmap thumb = (w > 100 || h > 100) ? App::pixmapFromImageInPlace(fullimage.scaled(100, 100, Qt::KeepAspectRatio, Qt::SmoothTransformation)) : QPixmap::fromImage(fullimage);
|
||||
photoThumbs.insert('s', thumb);
|
||||
photoSizes.push_back(MTP_photoSize(MTP_string("s"), MTP_fileLocationUnavailable(MTP_long(0), MTP_int(0), MTP_long(0)), MTP_int(thumb.width()), MTP_int(thumb.height()), MTP_int(0)));
|
||||
|
||||
QPixmap medium = (w > 320 || h > 320) ? QPixmap::fromImage(fullimage.scaled(320, 320, Qt::KeepAspectRatio, Qt::SmoothTransformation), Qt::ColorOnly) : QPixmap::fromImage(fullimage);
|
||||
QPixmap medium = (w > 320 || h > 320) ? App::pixmapFromImageInPlace(fullimage.scaled(320, 320, Qt::KeepAspectRatio, Qt::SmoothTransformation)) : QPixmap::fromImage(fullimage);
|
||||
photoThumbs.insert('m', medium);
|
||||
photoSizes.push_back(MTP_photoSize(MTP_string("m"), MTP_fileLocationUnavailable(MTP_long(0), MTP_int(0), MTP_long(0)), MTP_int(medium.width()), MTP_int(medium.height()), MTP_int(0)));
|
||||
|
||||
QPixmap full = (w > 1280 || h > 1280) ? QPixmap::fromImage(fullimage.scaled(1280, 1280, Qt::KeepAspectRatio, Qt::SmoothTransformation), Qt::ColorOnly) : QPixmap::fromImage(fullimage);
|
||||
QPixmap full = (w > 1280 || h > 1280) ? App::pixmapFromImageInPlace(fullimage.scaled(1280, 1280, Qt::KeepAspectRatio, Qt::SmoothTransformation)) : QPixmap::fromImage(fullimage);
|
||||
photoThumbs.insert('y', full);
|
||||
photoSizes.push_back(MTP_photoSize(MTP_string("y"), MTP_fileLocationUnavailable(MTP_long(0), MTP_int(0), MTP_long(0)), MTP_int(full.width()), MTP_int(full.height()), MTP_int(0)));
|
||||
|
||||
|
@ -396,7 +397,7 @@ void FileLoadTask::process() {
|
|||
thumbname = qsl("thumb.webp");
|
||||
}
|
||||
|
||||
QPixmap full = (w > 90 || h > 90) ? QPixmap::fromImage(fullimage.scaled(90, 90, Qt::KeepAspectRatio, Qt::SmoothTransformation), Qt::ColorOnly) : QPixmap::fromImage(fullimage, Qt::ColorOnly);
|
||||
QPixmap full = (w > 90 || h > 90) ? App::pixmapFromImageInPlace(fullimage.scaled(90, 90, Qt::KeepAspectRatio, Qt::SmoothTransformation)) : QPixmap::fromImage(fullimage, Qt::ColorOnly);
|
||||
|
||||
{
|
||||
QBuffer buffer(&thumbdata);
|
||||
|
@ -413,9 +414,9 @@ void FileLoadTask::process() {
|
|||
if (voice) {
|
||||
attributes[0] = MTP_documentAttributeAudio(MTP_flags(MTPDdocumentAttributeAudio::Flag::f_voice | MTPDdocumentAttributeAudio::Flag::f_waveform), MTP_int(_duration), MTPstring(), MTPstring(), MTP_bytes(documentWaveformEncode5bit(_waveform)));
|
||||
attributes.resize(1);
|
||||
document = MTP_document(MTP_long(_id), MTP_long(0), MTP_int(unixtime()), MTP_string(filemime), MTP_int(filesize), thumbSize, MTP_int(MTP::maindc()), MTP_vector<MTPDocumentAttribute>(attributes));
|
||||
document = MTP_document(MTP_long(_id), MTP_long(0), MTP_int(unixtime()), MTP_string(filemime), MTP_int(filesize), thumbSize, MTP_int(MTP::maindc()), MTP_int(0), MTP_vector<MTPDocumentAttribute>(attributes));
|
||||
} else {
|
||||
document = MTP_document(MTP_long(_id), MTP_long(0), MTP_int(unixtime()), MTP_string(filemime), MTP_int(filesize), thumbSize, MTP_int(MTP::maindc()), MTP_vector<MTPDocumentAttribute>(attributes));
|
||||
document = MTP_document(MTP_long(_id), MTP_long(0), MTP_int(unixtime()), MTP_string(filemime), MTP_int(filesize), thumbSize, MTP_int(MTP::maindc()), MTP_int(0), MTP_vector<MTPDocumentAttribute>(attributes));
|
||||
if (photo.type() == mtpc_photoEmpty) {
|
||||
_type = PrepareDocument;
|
||||
}
|
||||
|
|
|
@ -479,11 +479,12 @@ namespace {
|
|||
lskBackground = 0x08, // no data
|
||||
lskUserSettings = 0x09, // no data
|
||||
lskRecentHashtagsAndBots = 0x0a, // no data
|
||||
lskStickers = 0x0b, // no data
|
||||
lskStickersOld = 0x0b, // no data
|
||||
lskSavedPeers = 0x0c, // no data
|
||||
lskReportSpamStatuses = 0x0d, // no data
|
||||
lskSavedGifsOld = 0x0e, // no data
|
||||
lskSavedGifs = 0x0f, // no data
|
||||
lskStickersKeys = 0x10, // no data
|
||||
};
|
||||
|
||||
enum {
|
||||
|
@ -541,6 +542,8 @@ namespace {
|
|||
dbiHiddenPinnedMessages = 0x39,
|
||||
dbiDialogsMode = 0x40,
|
||||
dbiModerateMode = 0x41,
|
||||
dbiVideoVolume = 0x42,
|
||||
dbiStickersRecentLimit = 0x43,
|
||||
|
||||
dbiEncryptedWithSalt = 333,
|
||||
dbiEncrypted = 444,
|
||||
|
@ -570,7 +573,9 @@ namespace {
|
|||
uint64 _storageWebFilesSize = 0;
|
||||
FileKey _locationsKey = 0, _reportSpamStatusesKey = 0;
|
||||
|
||||
FileKey _recentStickersKeyOld = 0, _stickersKey = 0, _savedGifsKey = 0;
|
||||
FileKey _recentStickersKeyOld = 0;
|
||||
FileKey _installedStickersKey = 0, _featuredStickersKey = 0, _recentStickersKey = 0, _archivedStickersKey = 0;
|
||||
FileKey _savedGifsKey = 0;
|
||||
|
||||
FileKey _backgroundKey = 0;
|
||||
bool _backgroundWasRead = false;
|
||||
|
@ -834,6 +839,14 @@ namespace {
|
|||
Global::SetSavedGifsLimit(limit);
|
||||
} break;
|
||||
|
||||
case dbiStickersRecentLimit: {
|
||||
qint32 limit;
|
||||
stream >> limit;
|
||||
if (!_checkStreamStatus(stream)) return false;
|
||||
|
||||
Global::SetStickersRecentLimit(limit);
|
||||
} break;
|
||||
|
||||
case dbiMegagroupSizeMax: {
|
||||
qint32 maxSize;
|
||||
stream >> maxSize;
|
||||
|
@ -1308,7 +1321,15 @@ namespace {
|
|||
stream >> v;
|
||||
if (!_checkStreamStatus(stream)) return false;
|
||||
|
||||
cSetSongVolume(snap(v / 1e6, 0., 1.));
|
||||
Global::SetSongVolume(snap(v / 1e6, 0., 1.));
|
||||
} break;
|
||||
|
||||
case dbiVideoVolume: {
|
||||
qint32 v;
|
||||
stream >> v;
|
||||
if (!_checkStreamStatus(stream)) return false;
|
||||
|
||||
Global::SetVideoVolume(snap(v / 1e6, 0., 1.));
|
||||
} break;
|
||||
|
||||
default:
|
||||
|
@ -1532,7 +1553,7 @@ namespace {
|
|||
_writeMap(WriteMapFast);
|
||||
}
|
||||
|
||||
uint32 size = 17 * (sizeof(quint32) + sizeof(qint32));
|
||||
uint32 size = 18 * (sizeof(quint32) + sizeof(qint32));
|
||||
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));
|
||||
|
@ -1561,7 +1582,8 @@ namespace {
|
|||
data.stream << quint32(dbiDownloadPath) << (cAskDownloadPath() ? QString() : cDownloadPath()) << (cAskDownloadPath() ? QByteArray() : cDownloadPathBookmark());
|
||||
data.stream << quint32(dbiCompressPastedImage) << qint32(cCompressPastedImage());
|
||||
data.stream << quint32(dbiDialogLastPath) << cDialogLastPath();
|
||||
data.stream << quint32(dbiSongVolume) << qint32(qRound(cSongVolume() * 1e6));
|
||||
data.stream << quint32(dbiSongVolume) << qint32(qRound(Global::SongVolume() * 1e6));
|
||||
data.stream << quint32(dbiVideoVolume) << qint32(qRound(Global::VideoVolume() * 1e6));
|
||||
data.stream << quint32(dbiAutoDownload) << qint32(cAutoDownloadPhoto()) << qint32(cAutoDownloadAudio()) << qint32(cAutoDownloadGif());
|
||||
data.stream << quint32(dbiDialogsMode) << qint32(Global::DialogsModeEnabled() ? 1 : 0) << static_cast<qint32>(Global::DialogsMode());
|
||||
data.stream << quint32(dbiModerateMode) << qint32(Global::ModerateModeEnabled() ? 1 : 0);
|
||||
|
@ -1715,7 +1737,9 @@ namespace {
|
|||
StorageMap imagesMap, stickerImagesMap, audiosMap;
|
||||
qint64 storageImagesSize = 0, storageStickersSize = 0, storageAudiosSize = 0;
|
||||
quint64 locationsKey = 0, reportSpamStatusesKey = 0;
|
||||
quint64 recentStickersKeyOld = 0, stickersKey = 0, savedGifsKey = 0;
|
||||
quint64 recentStickersKeyOld = 0;
|
||||
quint64 installedStickersKey = 0, featuredStickersKey = 0, recentStickersKey = 0, archivedStickersKey = 0;
|
||||
quint64 savedGifsKey = 0;
|
||||
quint64 backgroundKey = 0, userSettingsKey = 0, recentHashtagsAndBotsKey = 0, savedPeersKey = 0;
|
||||
while (!map.stream.atEnd()) {
|
||||
quint32 keyType;
|
||||
|
@ -1796,8 +1820,11 @@ namespace {
|
|||
case lskRecentHashtagsAndBots: {
|
||||
map.stream >> recentHashtagsAndBotsKey;
|
||||
} break;
|
||||
case lskStickers: {
|
||||
map.stream >> stickersKey;
|
||||
case lskStickersOld: {
|
||||
map.stream >> installedStickersKey;
|
||||
} break;
|
||||
case lskStickersKeys: {
|
||||
map.stream >> installedStickersKey >> featuredStickersKey >> recentStickersKey >> archivedStickersKey;
|
||||
} break;
|
||||
case lskSavedGifsOld: {
|
||||
quint64 key;
|
||||
|
@ -1832,7 +1859,10 @@ namespace {
|
|||
_locationsKey = locationsKey;
|
||||
_reportSpamStatusesKey = reportSpamStatusesKey;
|
||||
_recentStickersKeyOld = recentStickersKeyOld;
|
||||
_stickersKey = stickersKey;
|
||||
_installedStickersKey = installedStickersKey;
|
||||
_featuredStickersKey = featuredStickersKey;
|
||||
_recentStickersKey = recentStickersKey;
|
||||
_archivedStickersKey = archivedStickersKey;
|
||||
_savedGifsKey = savedGifsKey;
|
||||
_savedPeersKey = savedPeersKey;
|
||||
_backgroundKey = backgroundKey;
|
||||
|
@ -1905,7 +1935,9 @@ namespace {
|
|||
if (_locationsKey) mapSize += sizeof(quint32) + sizeof(quint64);
|
||||
if (_reportSpamStatusesKey) mapSize += sizeof(quint32) + sizeof(quint64);
|
||||
if (_recentStickersKeyOld) mapSize += sizeof(quint32) + sizeof(quint64);
|
||||
if (_stickersKey) mapSize += sizeof(quint32) + sizeof(quint64);
|
||||
if (_installedStickersKey || _featuredStickersKey || _recentStickersKey || _archivedStickersKey) {
|
||||
mapSize += sizeof(quint32) + 4 * sizeof(quint64);
|
||||
}
|
||||
if (_savedGifsKey) mapSize += sizeof(quint32) + sizeof(quint64);
|
||||
if (_savedPeersKey) mapSize += sizeof(quint32) + sizeof(quint64);
|
||||
if (_backgroundKey) mapSize += sizeof(quint32) + sizeof(quint64);
|
||||
|
@ -1951,8 +1983,9 @@ namespace {
|
|||
if (_recentStickersKeyOld) {
|
||||
mapData.stream << quint32(lskRecentStickersOld) << quint64(_recentStickersKeyOld);
|
||||
}
|
||||
if (_stickersKey) {
|
||||
mapData.stream << quint32(lskStickers) << quint64(_stickersKey);
|
||||
if (_installedStickersKey || _featuredStickersKey || _recentStickersKey || _archivedStickersKey) {
|
||||
mapData.stream << quint32(lskStickersKeys);
|
||||
mapData.stream << quint64(_installedStickersKey) << quint64(_featuredStickersKey) << quint64(_recentStickersKey) << quint64(_archivedStickersKey);
|
||||
}
|
||||
if (_savedGifsKey) {
|
||||
mapData.stream << quint32(lskSavedGifs) << quint64(_savedGifsKey);
|
||||
|
@ -2175,12 +2208,13 @@ namespace Local {
|
|||
size += Serialize::stringSize(proxy.host) + sizeof(qint32) + Serialize::stringSize(proxy.user) + Serialize::stringSize(proxy.password);
|
||||
}
|
||||
|
||||
size += sizeof(quint32) + sizeof(qint32) * 6;
|
||||
size += sizeof(quint32) + sizeof(qint32) * 7;
|
||||
|
||||
EncryptedDescriptor data(size);
|
||||
data.stream << quint32(dbiChatSizeMax) << qint32(Global::ChatSizeMax());
|
||||
data.stream << quint32(dbiMegagroupSizeMax) << qint32(Global::MegagroupSizeMax());
|
||||
data.stream << quint32(dbiSavedGifsLimit) << qint32(Global::SavedGifsLimit());
|
||||
data.stream << quint32(dbiStickersRecentLimit) << qint32(Global::StickersRecentLimit());
|
||||
data.stream << quint32(dbiAutoStart) << qint32(cAutoStart());
|
||||
data.stream << quint32(dbiStartMinimized) << qint32(cStartMinimized());
|
||||
data.stream << quint32(dbiSendToMenu) << qint32(cSendToMenu());
|
||||
|
@ -2237,7 +2271,9 @@ namespace Local {
|
|||
_webFilesMap.clear();
|
||||
_storageWebFilesSize = 0;
|
||||
_locationsKey = _reportSpamStatusesKey = 0;
|
||||
_recentStickersKeyOld = _stickersKey = _savedGifsKey = 0;
|
||||
_recentStickersKeyOld = 0;
|
||||
_installedStickersKey = _featuredStickersKey = _recentStickersKey = _archivedStickersKey = 0;
|
||||
_savedGifsKey = 0;
|
||||
_backgroundKey = _userSettingsKey = _recentHashtagsAndBotsKey = _savedPeersKey = 0;
|
||||
_oldMapVersion = _oldSettingsVersion = 0;
|
||||
_mapChanged = true;
|
||||
|
@ -2633,7 +2669,7 @@ namespace Local {
|
|||
case StorageFileWebp: guessFormat = "WEBP"; break;
|
||||
default: guessFormat = QByteArray(); break;
|
||||
}
|
||||
pixmap = QPixmap::fromImage(App::readImage(data, &guessFormat, false), Qt::ColorOnly);
|
||||
pixmap = App::pixmapFromImageInPlace(App::readImage(data, &guessFormat, false));
|
||||
if (!pixmap.isNull()) {
|
||||
format = guessFormat;
|
||||
}
|
||||
|
@ -2900,7 +2936,7 @@ namespace Local {
|
|||
struct Result {
|
||||
Result(StorageFileType type, const QByteArray &data) : image(type, data) {
|
||||
QByteArray guessFormat;
|
||||
pixmap = QPixmap::fromImage(App::readImage(data, &guessFormat, false), Qt::ColorOnly);
|
||||
pixmap = App::pixmapFromImageInPlace(App::readImage(data, &guessFormat, false));
|
||||
if (!pixmap.isNull()) {
|
||||
format = guessFormat;
|
||||
}
|
||||
|
@ -3007,26 +3043,23 @@ namespace Local {
|
|||
}
|
||||
}
|
||||
|
||||
void _writeStickerSet(QDataStream &stream, uint64 setId) {
|
||||
auto it = Global::StickerSets().constFind(setId);
|
||||
if (it == Global::StickerSets().cend()) return;
|
||||
|
||||
bool notLoaded = (it->flags & MTPDstickerSet_ClientFlag::f_not_loaded);
|
||||
void _writeStickerSet(QDataStream &stream, const Stickers::Set &set) {
|
||||
bool notLoaded = (set.flags & MTPDstickerSet_ClientFlag::f_not_loaded);
|
||||
if (notLoaded) {
|
||||
stream << quint64(it->id) << quint64(it->access) << it->title << it->shortName << qint32(-it->count) << qint32(it->hash) << qint32(it->flags);
|
||||
stream << quint64(set.id) << quint64(set.access) << set.title << set.shortName << qint32(-set.count) << qint32(set.hash) << qint32(set.flags);
|
||||
return;
|
||||
} else {
|
||||
if (it->stickers.isEmpty()) return;
|
||||
if (set.stickers.isEmpty()) return;
|
||||
}
|
||||
|
||||
stream << quint64(it->id) << quint64(it->access) << it->title << it->shortName << qint32(it->stickers.size()) << qint32(it->hash) << qint32(it->flags);
|
||||
for (StickerPack::const_iterator j = it->stickers.cbegin(), e = it->stickers.cend(); j != e; ++j) {
|
||||
stream << quint64(set.id) << quint64(set.access) << set.title << set.shortName << qint32(set.stickers.size()) << qint32(set.hash) << qint32(set.flags);
|
||||
for (StickerPack::const_iterator j = set.stickers.cbegin(), e = set.stickers.cend(); j != e; ++j) {
|
||||
Serialize::Document::writeToStream(stream, *j);
|
||||
}
|
||||
|
||||
if (AppVersion > 9018) {
|
||||
stream << qint32(it->emoji.size());
|
||||
for (StickersByEmojiMap::const_iterator j = it->emoji.cbegin(), e = it->emoji.cend(); j != e; ++j) {
|
||||
stream << qint32(set.emoji.size());
|
||||
for (StickersByEmojiMap::const_iterator j = set.emoji.cbegin(), e = set.emoji.cend(); j != e; ++j) {
|
||||
stream << emojiString(j.key()) << qint32(j->size());
|
||||
for (int32 k = 0, l = j->size(); k < l; ++k) {
|
||||
stream << quint64(j->at(k)->id);
|
||||
|
@ -3035,61 +3068,274 @@ namespace Local {
|
|||
}
|
||||
}
|
||||
|
||||
void writeStickers() {
|
||||
// In generic method _writeStickerSets() we look through all the sets and call a
|
||||
// callback on each set to see, if we write it, skip it or abort the whole write.
|
||||
enum class StickerSetCheckResult {
|
||||
Write,
|
||||
Skip,
|
||||
Abort,
|
||||
};
|
||||
|
||||
// CheckSet is a functor on Stickers::Set, which returns a StickerSetCheckResult.
|
||||
template <typename CheckSet>
|
||||
void _writeStickerSets(FileKey &stickersKey, CheckSet checkSet, const Stickers::Order &order) {
|
||||
if (!_working()) return;
|
||||
|
||||
const Stickers::Sets &sets(Global::StickerSets());
|
||||
auto &sets = Global::StickerSets();
|
||||
if (sets.isEmpty()) {
|
||||
if (_stickersKey) {
|
||||
clearKey(_stickersKey);
|
||||
_stickersKey = 0;
|
||||
if (stickersKey) {
|
||||
clearKey(stickersKey);
|
||||
stickersKey = 0;
|
||||
_mapChanged = true;
|
||||
}
|
||||
_writeMap();
|
||||
} else {
|
||||
int32 setsCount = 0;
|
||||
QByteArray 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) {
|
||||
if (!(i->flags & MTPDstickerSet::Flag::f_disabled) || (i->flags & MTPDstickerSet::Flag::f_official)) { // waiting to receive
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (i->stickers.isEmpty()) continue;
|
||||
}
|
||||
|
||||
// id + access + title + shortName + stickersCount + hash + flags
|
||||
size += sizeof(quint64) * 2 + Serialize::stringSize(i->title) + Serialize::stringSize(i->shortName) + sizeof(quint32) + sizeof(qint32) * 2;
|
||||
for (StickerPack::const_iterator j = i->stickers.cbegin(), e = i->stickers.cend(); j != e; ++j) {
|
||||
size += Serialize::Document::sizeInStream(*j);
|
||||
}
|
||||
|
||||
if (AppVersion > 9018) {
|
||||
size += sizeof(qint32); // emojiCount
|
||||
for (StickersByEmojiMap::const_iterator j = i->emoji.cbegin(), e = i->emoji.cend(); j != e; ++j) {
|
||||
size += Serialize::stringSize(emojiString(j.key())) + sizeof(qint32) + (j->size() * sizeof(quint64));
|
||||
}
|
||||
}
|
||||
|
||||
++setsCount;
|
||||
}
|
||||
|
||||
if (!_stickersKey) {
|
||||
_stickersKey = genKey();
|
||||
_mapChanged = true;
|
||||
_writeMap(WriteMapFast);
|
||||
}
|
||||
EncryptedDescriptor data(size);
|
||||
data.stream << quint32(setsCount) << hashToWrite;
|
||||
_writeStickerSet(data.stream, Stickers::CustomSetId);
|
||||
for (auto i = Global::StickerSetsOrder().cbegin(), e = Global::StickerSetsOrder().cend(); i != e; ++i) {
|
||||
_writeStickerSet(data.stream, *i);
|
||||
}
|
||||
FileWriteDescriptor file(_stickersKey);
|
||||
file.writeEncrypted(data);
|
||||
return;
|
||||
}
|
||||
int32 setsCount = 0;
|
||||
QByteArray hashToWrite;
|
||||
quint32 size = sizeof(quint32) + Serialize::bytearraySize(hashToWrite);
|
||||
for_const (auto &set, sets) {
|
||||
auto result = checkSet(set);
|
||||
if (result == StickerSetCheckResult::Abort) {
|
||||
return;
|
||||
} else if (result == StickerSetCheckResult::Skip) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// id + access + title + shortName + stickersCount + hash + flags
|
||||
size += sizeof(quint64) * 2 + Serialize::stringSize(set.title) + Serialize::stringSize(set.shortName) + sizeof(quint32) + sizeof(qint32) * 2;
|
||||
for_const (auto &sticker, set.stickers) {
|
||||
size += Serialize::Document::sizeInStream(sticker);
|
||||
}
|
||||
|
||||
size += sizeof(qint32); // emojiCount
|
||||
for (auto j = set.emoji.cbegin(), e = set.emoji.cend(); j != e; ++j) {
|
||||
size += Serialize::stringSize(emojiString(j.key())) + sizeof(qint32) + (j->size() * sizeof(quint64));
|
||||
}
|
||||
|
||||
++setsCount;
|
||||
}
|
||||
if (!setsCount && order.isEmpty()) {
|
||||
if (stickersKey) {
|
||||
clearKey(stickersKey);
|
||||
stickersKey = 0;
|
||||
_mapChanged = true;
|
||||
}
|
||||
_writeMap();
|
||||
return;
|
||||
}
|
||||
size += sizeof(qint32) + (order.size() * sizeof(quint64));
|
||||
|
||||
if (!stickersKey) {
|
||||
stickersKey = genKey();
|
||||
_mapChanged = true;
|
||||
_writeMap(WriteMapFast);
|
||||
}
|
||||
EncryptedDescriptor data(size);
|
||||
data.stream << quint32(setsCount) << hashToWrite;
|
||||
for_const (auto &set, sets) {
|
||||
auto result = checkSet(set);
|
||||
if (result == StickerSetCheckResult::Abort) {
|
||||
return;
|
||||
} else if (result == StickerSetCheckResult::Skip) {
|
||||
continue;
|
||||
}
|
||||
_writeStickerSet(data.stream, set);
|
||||
}
|
||||
data.stream << order;
|
||||
|
||||
FileWriteDescriptor file(stickersKey);
|
||||
file.writeEncrypted(data);
|
||||
}
|
||||
|
||||
void _readStickerSets(FileKey &stickersKey, Stickers::Order *outOrder = nullptr, MTPDstickerSet::Flags readingFlags = 0) {
|
||||
FileReadDescriptor stickers;
|
||||
if (!readEncryptedFile(stickers, stickersKey)) {
|
||||
clearKey(stickersKey);
|
||||
stickersKey = 0;
|
||||
_writeMap();
|
||||
return;
|
||||
}
|
||||
|
||||
bool readingInstalled = (readingFlags == qFlags(MTPDstickerSet::Flag::f_installed));
|
||||
|
||||
auto &sets = Global::RefStickerSets();
|
||||
if (outOrder) outOrder->clear();
|
||||
|
||||
quint32 cnt;
|
||||
QByteArray hash;
|
||||
stickers.stream >> cnt >> hash; // ignore hash, it is counted
|
||||
if (readingInstalled && stickers.version < 8019) { // bad data in old caches
|
||||
cnt += 2; // try to read at least something
|
||||
}
|
||||
for (uint32 i = 0; i < cnt; ++i) {
|
||||
quint64 setId = 0, setAccess = 0;
|
||||
QString setTitle, setShortName;
|
||||
qint32 scnt = 0;
|
||||
stickers.stream >> setId >> setAccess >> setTitle >> setShortName >> scnt;
|
||||
|
||||
qint32 setHash = 0, setFlags = 0;
|
||||
if (stickers.version > 8033) {
|
||||
stickers.stream >> setHash >> setFlags;
|
||||
if (setFlags & qFlags(MTPDstickerSet_ClientFlag::f_not_loaded__old)) {
|
||||
setFlags &= ~qFlags(MTPDstickerSet_ClientFlag::f_not_loaded__old);
|
||||
setFlags |= qFlags(MTPDstickerSet_ClientFlag::f_not_loaded);
|
||||
}
|
||||
}
|
||||
if (readingInstalled && stickers.version < 9061) {
|
||||
setFlags |= qFlags(MTPDstickerSet::Flag::f_installed);
|
||||
}
|
||||
|
||||
if (setId == Stickers::DefaultSetId) {
|
||||
setTitle = lang(lng_stickers_default_set);
|
||||
setFlags |= qFlags(MTPDstickerSet::Flag::f_official | MTPDstickerSet_ClientFlag::f_special);
|
||||
if (readingInstalled && outOrder && stickers.version < 9061) {
|
||||
outOrder->push_front(setId);
|
||||
}
|
||||
} else if (setId == Stickers::CustomSetId) {
|
||||
setTitle = lang(lng_custom_stickers);
|
||||
setFlags |= qFlags(MTPDstickerSet_ClientFlag::f_special);
|
||||
} else if (setId == Stickers::CloudRecentSetId) {
|
||||
setTitle = lang(lng_recent_stickers);
|
||||
setFlags |= qFlags(MTPDstickerSet_ClientFlag::f_special);
|
||||
} else if (setId) {
|
||||
if (readingInstalled && outOrder && stickers.version < 9061) {
|
||||
outOrder->push_back(setId);
|
||||
}
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto it = sets.find(setId);
|
||||
if (it == sets.cend()) {
|
||||
// We will set this flags from order lists when reading those stickers.
|
||||
setFlags &= ~(MTPDstickerSet::Flag::f_installed | MTPDstickerSet_ClientFlag::f_featured);
|
||||
it = sets.insert(setId, Stickers::Set(setId, setAccess, setTitle, setShortName, 0, setHash, MTPDstickerSet::Flags(setFlags)));
|
||||
}
|
||||
auto &set = it.value();
|
||||
|
||||
if (scnt < 0) { // disabled not loaded set
|
||||
if (!set.count || set.stickers.isEmpty()) {
|
||||
set.count = -scnt;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
bool fillStickers = set.stickers.isEmpty();
|
||||
if (fillStickers) {
|
||||
set.stickers.reserve(scnt);
|
||||
set.count = 0;
|
||||
}
|
||||
|
||||
Serialize::Document::StickerSetInfo info(setId, setAccess, setShortName);
|
||||
OrderedSet<DocumentId> read;
|
||||
for (int32 j = 0; j < scnt; ++j) {
|
||||
auto document = Serialize::Document::readStickerFromStream(stickers.version, stickers.stream, info);
|
||||
if (!document || !document->sticker()) continue;
|
||||
|
||||
if (read.contains(document->id)) continue;
|
||||
read.insert(document->id);
|
||||
|
||||
if (fillStickers) {
|
||||
set.stickers.push_back(document);
|
||||
++set.count;
|
||||
}
|
||||
}
|
||||
|
||||
if (stickers.version > 9018) {
|
||||
qint32 emojiCount;
|
||||
stickers.stream >> emojiCount;
|
||||
for (int32 j = 0; j < emojiCount; ++j) {
|
||||
QString emojiString;
|
||||
qint32 stickersCount;
|
||||
stickers.stream >> emojiString >> stickersCount;
|
||||
StickerPack pack;
|
||||
pack.reserve(stickersCount);
|
||||
for (int32 k = 0; k < stickersCount; ++k) {
|
||||
quint64 id;
|
||||
stickers.stream >> id;
|
||||
DocumentData *doc = App::document(id);
|
||||
if (!doc || !doc->sticker()) continue;
|
||||
|
||||
pack.push_back(doc);
|
||||
}
|
||||
if (fillStickers) {
|
||||
if (auto e = emojiGetNoColor(emojiFromText(emojiString))) {
|
||||
set.emoji.insert(e, pack);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Read orders of installed and featured stickers.
|
||||
if (outOrder && stickers.version >= 9061) {
|
||||
stickers.stream >> *outOrder;
|
||||
}
|
||||
|
||||
// Set flags that we dropped above from the order.
|
||||
if (readingFlags && outOrder) {
|
||||
for_const (auto setId, *outOrder) {
|
||||
auto it = sets.find(setId);
|
||||
if (it != sets.cend()) {
|
||||
it->flags |= readingFlags;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void writeInstalledStickers() {
|
||||
_writeStickerSets(_installedStickersKey, [](const Stickers::Set &set) {
|
||||
if (set.id == Stickers::CloudRecentSetId) { // separate file for recent
|
||||
return StickerSetCheckResult::Skip;
|
||||
} else if (set.flags & MTPDstickerSet_ClientFlag::f_special) {
|
||||
if (set.stickers.isEmpty()) { // all other special are "installed"
|
||||
return StickerSetCheckResult::Skip;
|
||||
}
|
||||
} else if (!(set.flags & MTPDstickerSet::Flag::f_installed) || (set.flags & MTPDstickerSet::Flag::f_archived)) {
|
||||
return StickerSetCheckResult::Skip;
|
||||
} else if (set.flags & MTPDstickerSet_ClientFlag::f_not_loaded) { // waiting to receive
|
||||
return StickerSetCheckResult::Abort;
|
||||
} else if (set.stickers.isEmpty()) {
|
||||
return StickerSetCheckResult::Skip;
|
||||
}
|
||||
return StickerSetCheckResult::Write;
|
||||
}, Global::StickerSetsOrder());
|
||||
}
|
||||
|
||||
void writeFeaturedStickers() {
|
||||
_writeStickerSets(_featuredStickersKey, [](const Stickers::Set &set) {
|
||||
if (set.id == Stickers::CloudRecentSetId) { // separate file for recent
|
||||
return StickerSetCheckResult::Skip;
|
||||
} else if (set.flags & MTPDstickerSet_ClientFlag::f_special) {
|
||||
return StickerSetCheckResult::Skip;
|
||||
} else if (!(set.flags & MTPDstickerSet_ClientFlag::f_featured)) {
|
||||
return StickerSetCheckResult::Skip;
|
||||
} else if (set.flags & MTPDstickerSet_ClientFlag::f_not_loaded) { // waiting to receive
|
||||
return StickerSetCheckResult::Abort;
|
||||
} else if (set.stickers.isEmpty()) {
|
||||
return StickerSetCheckResult::Skip;
|
||||
}
|
||||
return StickerSetCheckResult::Write;
|
||||
}, Global::FeaturedStickerSetsOrder());
|
||||
}
|
||||
|
||||
void writeRecentStickers() {
|
||||
_writeStickerSets(_recentStickersKey, [](const Stickers::Set &set) {
|
||||
if (set.id != Stickers::CloudRecentSetId || set.stickers.isEmpty()) {
|
||||
return StickerSetCheckResult::Skip;
|
||||
}
|
||||
return StickerSetCheckResult::Write;
|
||||
}, Stickers::Order());
|
||||
}
|
||||
|
||||
void writeArchivedStickers() {
|
||||
_writeStickerSets(_archivedStickersKey, [](const Stickers::Set &set) {
|
||||
if (!(set.flags & MTPDstickerSet::Flag::f_archived) || set.stickers.isEmpty()) {
|
||||
return StickerSetCheckResult::Skip;
|
||||
}
|
||||
return StickerSetCheckResult::Write;
|
||||
}, Global::ArchivedStickerSetsOrder());
|
||||
}
|
||||
|
||||
void importOldRecentStickers() {
|
||||
|
@ -3103,17 +3349,17 @@ namespace Local {
|
|||
return;
|
||||
}
|
||||
|
||||
Stickers::Sets &sets(Global::RefStickerSets());
|
||||
auto &sets = Global::RefStickerSets();
|
||||
sets.clear();
|
||||
|
||||
Stickers::Order &order(Global::RefStickerSetsOrder());
|
||||
auto &order = Global::RefStickerSetsOrder();
|
||||
order.clear();
|
||||
|
||||
RecentStickerPack &recent(cRefRecentStickers());
|
||||
auto &recent = cRefRecentStickers();
|
||||
recent.clear();
|
||||
|
||||
Stickers::Set &def(sets.insert(Stickers::DefaultSetId, Stickers::Set(Stickers::DefaultSetId, 0, lang(lng_stickers_default_set), QString(), 0, 0, MTPDstickerSet::Flag::f_official)).value());
|
||||
Stickers::Set &custom(sets.insert(Stickers::CustomSetId, Stickers::Set(Stickers::CustomSetId, 0, lang(lng_custom_stickers), QString(), 0, 0, 0)).value());
|
||||
auto &def = sets.insert(Stickers::DefaultSetId, Stickers::Set(Stickers::DefaultSetId, 0, lang(lng_stickers_default_set), QString(), 0, 0, MTPDstickerSet::Flag::f_official | MTPDstickerSet::Flag::f_installed | MTPDstickerSet_ClientFlag::f_special)).value();
|
||||
auto &custom = sets.insert(Stickers::CustomSetId, Stickers::Set(Stickers::CustomSetId, 0, lang(lng_custom_stickers), QString(), 0, 0, MTPDstickerSet::Flag::f_installed | MTPDstickerSet_ClientFlag::f_special)).value();
|
||||
|
||||
QMap<uint64, bool> read;
|
||||
while (!stickers.stream.atEnd()) {
|
||||
|
@ -3139,7 +3385,7 @@ namespace Local {
|
|||
attributes.push_back(MTP_documentAttributeImageSize(MTP_int(width), MTP_int(height)));
|
||||
}
|
||||
|
||||
DocumentData *doc = App::documentSet(id, 0, access, date, attributes, mime, ImagePtr(), dc, size, StorageImageLocation());
|
||||
DocumentData *doc = App::documentSet(id, 0, access, 0, date, attributes, mime, ImagePtr(), dc, size, StorageImageLocation());
|
||||
if (!doc->sticker()) continue;
|
||||
|
||||
if (value > 0) {
|
||||
|
@ -3149,7 +3395,9 @@ namespace Local {
|
|||
custom.stickers.push_back(doc);
|
||||
++custom.count;
|
||||
}
|
||||
if (recent.size() < StickerPanPerRow * StickerPanRowsPerPage && qAbs(value) > 1) recent.push_back(qMakePair(doc, qAbs(value)));
|
||||
if (recent.size() < Global::StickersRecentLimit() && qAbs(value) > 1) {
|
||||
recent.push_back(qMakePair(doc, qAbs(value)));
|
||||
}
|
||||
}
|
||||
if (def.stickers.isEmpty()) {
|
||||
sets.remove(Stickers::DefaultSetId);
|
||||
|
@ -3158,7 +3406,7 @@ namespace Local {
|
|||
}
|
||||
if (custom.stickers.isEmpty()) sets.remove(Stickers::CustomSetId);
|
||||
|
||||
writeStickers();
|
||||
writeInstalledStickers();
|
||||
writeUserSettings();
|
||||
|
||||
clearKey(_recentStickersKeyOld);
|
||||
|
@ -3166,130 +3414,95 @@ namespace Local {
|
|||
_writeMap();
|
||||
}
|
||||
|
||||
void readStickers() {
|
||||
if (!_stickersKey) {
|
||||
void readInstalledStickers() {
|
||||
if (!_installedStickersKey) {
|
||||
return importOldRecentStickers();
|
||||
}
|
||||
|
||||
FileReadDescriptor stickers;
|
||||
if (!readEncryptedFile(stickers, _stickersKey)) {
|
||||
clearKey(_stickersKey);
|
||||
_stickersKey = 0;
|
||||
_writeMap();
|
||||
return;
|
||||
Global::RefStickerSets().clear();
|
||||
_readStickerSets(_installedStickersKey, &Global::RefStickerSetsOrder(), qFlags(MTPDstickerSet::Flag::f_installed));
|
||||
}
|
||||
|
||||
void readFeaturedStickers() {
|
||||
_readStickerSets(_featuredStickersKey, &Global::RefFeaturedStickerSetsOrder(), qFlags(MTPDstickerSet_ClientFlag::f_featured));
|
||||
|
||||
auto &sets = Global::StickerSets();
|
||||
int unreadCount = 0;
|
||||
for_const (auto setId, Global::FeaturedStickerSetsOrder()) {
|
||||
auto it = sets.constFind(setId);
|
||||
if (it != sets.cend() && (it->flags & MTPDstickerSet_ClientFlag::f_unread)) {
|
||||
++unreadCount;
|
||||
}
|
||||
}
|
||||
Global::SetFeaturedStickerSetsUnreadCount(unreadCount);
|
||||
}
|
||||
|
||||
Stickers::Sets &sets(Global::RefStickerSets());
|
||||
sets.clear();
|
||||
void readRecentStickers() {
|
||||
_readStickerSets(_recentStickersKey);
|
||||
}
|
||||
|
||||
Stickers::Order &order(Global::RefStickerSetsOrder());
|
||||
order.clear();
|
||||
|
||||
quint32 cnt;
|
||||
QByteArray hash;
|
||||
stickers.stream >> cnt >> hash; // ignore hash, it is counted
|
||||
if (stickers.version < 8019) { // bad data in old caches
|
||||
cnt += 2; // try to read at least something
|
||||
}
|
||||
for (uint32 i = 0; i < cnt; ++i) {
|
||||
quint64 setId = 0, setAccess = 0;
|
||||
QString setTitle, setShortName;
|
||||
qint32 scnt = 0;
|
||||
stickers.stream >> setId >> setAccess >> setTitle >> setShortName >> scnt;
|
||||
|
||||
qint32 setHash = 0, setFlags = 0;
|
||||
if (stickers.version > 8033) {
|
||||
stickers.stream >> setHash >> setFlags;
|
||||
if (setFlags & qFlags(MTPDstickerSet_ClientFlag::f_not_loaded__old)) {
|
||||
setFlags &= ~qFlags(MTPDstickerSet_ClientFlag::f_not_loaded__old);
|
||||
setFlags |= qFlags(MTPDstickerSet_ClientFlag::f_not_loaded);
|
||||
}
|
||||
}
|
||||
|
||||
if (setId == Stickers::DefaultSetId) {
|
||||
setTitle = lang(lng_stickers_default_set);
|
||||
setFlags |= qFlags(MTPDstickerSet::Flag::f_official);
|
||||
order.push_front(setId);
|
||||
} else if (setId == Stickers::CustomSetId) {
|
||||
setTitle = lang(lng_custom_stickers);
|
||||
} else if (setId) {
|
||||
order.push_back(setId);
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
Stickers::Set &set(sets.insert(setId, Stickers::Set(setId, setAccess, setTitle, setShortName, 0, setHash, MTPDstickerSet::Flags(setFlags))).value());
|
||||
if (scnt < 0) { // disabled not loaded set
|
||||
set.count = -scnt;
|
||||
continue;
|
||||
}
|
||||
|
||||
set.stickers.reserve(scnt);
|
||||
|
||||
Serialize::Document::StickerSetInfo info(setId, setAccess, setShortName);
|
||||
OrderedSet<DocumentId> read;
|
||||
for (int32 j = 0; j < scnt; ++j) {
|
||||
auto document = Serialize::Document::readStickerFromStream(stickers.stream, info);
|
||||
if (!document || !document->sticker()) continue;
|
||||
|
||||
if (read.contains(document->id)) continue;
|
||||
read.insert(document->id);
|
||||
|
||||
set.stickers.push_back(document);
|
||||
++set.count;
|
||||
}
|
||||
|
||||
if (stickers.version > 9018) {
|
||||
qint32 emojiCount;
|
||||
stickers.stream >> emojiCount;
|
||||
for (int32 j = 0; j < emojiCount; ++j) {
|
||||
QString emojiString;
|
||||
qint32 stickersCount;
|
||||
stickers.stream >> emojiString >> stickersCount;
|
||||
StickerPack pack;
|
||||
pack.reserve(stickersCount);
|
||||
for (int32 k = 0; k < stickersCount; ++k) {
|
||||
quint64 id;
|
||||
stickers.stream >> id;
|
||||
DocumentData *doc = App::document(id);
|
||||
if (!doc || !doc->sticker()) continue;
|
||||
|
||||
pack.push_back(doc);
|
||||
}
|
||||
if (EmojiPtr e = emojiGetNoColor(emojiFromText(emojiString))) {
|
||||
set.emoji.insert(e, pack);
|
||||
}
|
||||
}
|
||||
}
|
||||
void readArchivedStickers() {
|
||||
static bool archivedStickersRead = false;
|
||||
if (!archivedStickersRead) {
|
||||
_readStickerSets(_archivedStickersKey, &Global::RefArchivedStickerSetsOrder());
|
||||
archivedStickersRead = true;
|
||||
}
|
||||
}
|
||||
|
||||
int32 countStickersHash(bool checkOfficial) {
|
||||
int32 countStickersHash(bool checkOutdatedInfo) {
|
||||
uint32 acc = 0;
|
||||
bool foundOfficial = false, foundBad = false;;
|
||||
const Stickers::Sets &sets(Global::StickerSets());
|
||||
const Stickers::Order &order(Global::StickerSetsOrder());
|
||||
bool foundOutdated = false;
|
||||
auto &sets = Global::StickerSets();
|
||||
auto &order = Global::StickerSetsOrder();
|
||||
for (auto i = order.cbegin(), e = order.cend(); i != e; ++i) {
|
||||
auto j = sets.constFind(*i);
|
||||
if (j != sets.cend()) {
|
||||
if (j->id == 0) {
|
||||
foundBad = true;
|
||||
} else if (j->flags & MTPDstickerSet::Flag::f_official) {
|
||||
foundOfficial = true;
|
||||
}
|
||||
if (!(j->flags & MTPDstickerSet::Flag::f_disabled)) {
|
||||
if (j->id == Stickers::DefaultSetId) {
|
||||
foundOutdated = true;
|
||||
} else if (!(j->flags & MTPDstickerSet_ClientFlag::f_special)
|
||||
&& !(j->flags & MTPDstickerSet::Flag::f_archived)) {
|
||||
acc = (acc * 20261) + j->hash;
|
||||
}
|
||||
}
|
||||
}
|
||||
return (!checkOfficial || (!foundBad && foundOfficial)) ? int32(acc & 0x7FFFFFFF) : 0;
|
||||
return (!checkOutdatedInfo || !foundOutdated) ? int32(acc & 0x7FFFFFFF) : 0;
|
||||
}
|
||||
|
||||
int32 countRecentStickersHash() {
|
||||
uint32 acc = 0;
|
||||
auto &sets = Global::StickerSets();
|
||||
auto it = sets.constFind(Stickers::CloudRecentSetId);
|
||||
if (it != sets.cend()) {
|
||||
for_const (auto doc, it->stickers) {
|
||||
auto docId = doc->id;
|
||||
acc = (acc * 20261) + uint32(docId >> 32);
|
||||
acc = (acc * 20261) + uint32(docId & 0xFFFFFFFF);
|
||||
}
|
||||
}
|
||||
return int32(acc & 0x7FFFFFFF);
|
||||
}
|
||||
|
||||
int32 countFeaturedStickersHash() {
|
||||
uint32 acc = 0;
|
||||
auto &sets = Global::StickerSets();
|
||||
auto &featured = Global::FeaturedStickerSetsOrder();
|
||||
for_const (auto setId, featured) {
|
||||
acc = (acc * 20261) + uint32(setId >> 32);
|
||||
acc = (acc * 20261) + uint32(setId & 0xFFFFFFFF);
|
||||
|
||||
auto it = sets.constFind(setId);
|
||||
if (it != sets.cend() && (it->flags & MTPDstickerSet_ClientFlag::f_unread)) {
|
||||
acc = (acc * 20261) + 1U;
|
||||
}
|
||||
}
|
||||
return int32(acc & 0x7FFFFFFF);
|
||||
}
|
||||
|
||||
int32 countSavedGifsHash() {
|
||||
uint32 acc = 0;
|
||||
const SavedGifs &saved(cSavedGifs());
|
||||
for (SavedGifs::const_iterator i = saved.cbegin(), e = saved.cend(); i != e; ++i) {
|
||||
uint64 docId = (*i)->id;
|
||||
|
||||
auto &saved = cSavedGifs();
|
||||
for_const (auto doc, saved) {
|
||||
auto docId = doc->id;
|
||||
acc = (acc * 20261) + uint32(docId >> 32);
|
||||
acc = (acc * 20261) + uint32(docId & 0xFFFFFFFF);
|
||||
}
|
||||
|
@ -3347,7 +3560,7 @@ namespace Local {
|
|||
saved.reserve(cnt);
|
||||
OrderedSet<DocumentId> read;
|
||||
for (uint32 i = 0; i < cnt; ++i) {
|
||||
DocumentData *document = Serialize::Document::readFromStream(gifs.stream);
|
||||
DocumentData *document = Serialize::Document::readFromStream(gifs.version, gifs.stream);
|
||||
if (!document || !document->isAnimation()) continue;
|
||||
|
||||
if (read.contains(document->id)) continue;
|
||||
|
@ -3852,8 +4065,8 @@ namespace Local {
|
|||
_recentStickersKeyOld = 0;
|
||||
_mapChanged = true;
|
||||
}
|
||||
if (_stickersKey) {
|
||||
_stickersKey = 0;
|
||||
if (_installedStickersKey || _featuredStickersKey || _recentStickersKey || _archivedStickersKey) {
|
||||
_installedStickersKey = _featuredStickersKey = _recentStickersKey = _archivedStickersKey = 0;
|
||||
_mapChanged = true;
|
||||
}
|
||||
if (_recentHashtagsAndBotsKey) {
|
||||
|
|
|
@ -153,9 +153,17 @@ namespace Local {
|
|||
|
||||
void cancelTask(TaskId id);
|
||||
|
||||
void writeStickers();
|
||||
void readStickers();
|
||||
int32 countStickersHash(bool checkOfficial = false);
|
||||
void writeInstalledStickers();
|
||||
void writeFeaturedStickers();
|
||||
void writeRecentStickers();
|
||||
void writeArchivedStickers();
|
||||
void readInstalledStickers();
|
||||
void readFeaturedStickers();
|
||||
void readRecentStickers();
|
||||
void readArchivedStickers();
|
||||
int32 countStickersHash(bool checkOutdatedInfo = false);
|
||||
int32 countRecentStickersHash();
|
||||
int32 countFeaturedStickersHash();
|
||||
|
||||
void writeSavedGifs();
|
||||
void readSavedGifs();
|
||||
|
|
|
@ -45,9 +45,12 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
#include "boxes/stickersetbox.h"
|
||||
#include "boxes/contactsbox.h"
|
||||
#include "boxes/downloadpathbox.h"
|
||||
#include "boxes/confirmphonebox.h"
|
||||
#include "localstorage.h"
|
||||
#include "shortcuts.h"
|
||||
#include "audio.h"
|
||||
#include "media/media_audio.h"
|
||||
#include "core/qthelp_regex.h"
|
||||
#include "core/qthelp_url.h"
|
||||
|
||||
StackItemSection::StackItemSection(std_::unique_ptr<Window::SectionMemento> &&memento) : StackItem(nullptr)
|
||||
, _memento(std_::move(memento)) {
|
||||
|
@ -56,6 +59,8 @@ StackItemSection::StackItemSection(std_::unique_ptr<Window::SectionMemento> &&me
|
|||
StackItemSection::~StackItemSection() {
|
||||
}
|
||||
|
||||
#include "boxes/confirmphonebox.h"
|
||||
|
||||
MainWidget::MainWidget(MainWindow *window) : TWidget(window)
|
||||
, _a_show(animation(this, &MainWidget::step_show))
|
||||
, _dialogsWidth(st::dialogsWidthMin)
|
||||
|
@ -91,9 +96,6 @@ MainWidget::MainWidget(MainWindow *window) : TWidget(window)
|
|||
connect(&updateNotifySettingTimer, SIGNAL(timeout()), this, SLOT(onUpdateNotifySettings()));
|
||||
if (audioPlayer()) {
|
||||
connect(audioPlayer(), SIGNAL(updated(const AudioMsgId&)), this, SLOT(audioPlayProgress(const AudioMsgId&)));
|
||||
connect(audioPlayer(), SIGNAL(stopped(const AudioMsgId&)), this, SLOT(audioPlayProgress(const AudioMsgId&)));
|
||||
connect(audioPlayer(), SIGNAL(updated(const SongMsgId&)), this, SLOT(documentPlayProgress(const SongMsgId&)));
|
||||
connect(audioPlayer(), SIGNAL(stopped(const SongMsgId&)), this, SLOT(documentPlayProgress(const SongMsgId&)));
|
||||
}
|
||||
connect(&_updateMutedTimer, SIGNAL(timeout()), this, SLOT(onUpdateMuted()));
|
||||
connect(&_viewsIncrementTimer, SIGNAL(timeout()), this, SLOT(onViewsIncrement()));
|
||||
|
@ -301,11 +303,6 @@ void MainWidget::finishForwarding(History *history, bool silent) {
|
|||
FullMsgId newId(peerToChannel(history->peer->id), clientMsgId());
|
||||
HistoryMessage *msg = static_cast<HistoryMessage*>(_toForward.cbegin().value());
|
||||
history->addNewForwarded(newId.msg, flags, date(MTP_int(unixtime())), showFromName ? MTP::authedId() : 0, msg);
|
||||
if (HistoryMedia *media = msg->getMedia()) {
|
||||
if (media->type() == MediaTypeSticker) {
|
||||
App::main()->incrementSticker(media->getDocument());
|
||||
}
|
||||
}
|
||||
App::historyRegRandom(randomId, newId);
|
||||
}
|
||||
if (forwardFrom != i.value()->history()->peer) {
|
||||
|
@ -518,16 +515,19 @@ void MainWidget::notify_handlePendingHistoryUpdate() {
|
|||
_history->notify_handlePendingHistoryUpdate();
|
||||
}
|
||||
|
||||
void MainWidget::cmd_search() {
|
||||
_history->cmd_search();
|
||||
bool MainWidget::cmd_search() {
|
||||
if (Ui::isLayerShown() || Ui::isMediaViewShown()) return false;
|
||||
return _history->cmd_search();
|
||||
}
|
||||
|
||||
void MainWidget::cmd_next_chat() {
|
||||
_history->cmd_next_chat();
|
||||
bool MainWidget::cmd_next_chat() {
|
||||
if (Ui::isLayerShown() || Ui::isMediaViewShown()) return false;
|
||||
return _history->cmd_next_chat();
|
||||
}
|
||||
|
||||
void MainWidget::cmd_previous_chat() {
|
||||
_history->cmd_previous_chat();
|
||||
bool MainWidget::cmd_previous_chat() {
|
||||
if (Ui::isLayerShown() || Ui::isMediaViewShown()) return false;
|
||||
return _history->cmd_previous_chat();
|
||||
}
|
||||
|
||||
void MainWidget::noHider(HistoryHider *destroyed) {
|
||||
|
@ -1022,13 +1022,13 @@ void MainWidget::onCacheBackground() {
|
|||
}
|
||||
_cachedX = 0;
|
||||
_cachedY = 0;
|
||||
_cachedBackground = QPixmap::fromImage(result);
|
||||
_cachedBackground = App::pixmapFromImageInPlace(std_::move(result));
|
||||
} else {
|
||||
QRect to, from;
|
||||
backgroundParams(_willCacheFor, to, from);
|
||||
_cachedX = to.x();
|
||||
_cachedY = to.y();
|
||||
_cachedBackground = QPixmap::fromImage(bg.toImage().copy(from).scaled(to.width() * cIntRetinaFactor(), to.height() * cIntRetinaFactor(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
|
||||
_cachedBackground = App::pixmapFromImageInPlace(bg.toImage().copy(from).scaled(to.width() * cIntRetinaFactor(), to.height() * cIntRetinaFactor(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
|
||||
_cachedBackground.setDevicePixelRatio(cRetinaFactor());
|
||||
}
|
||||
_cachedFor = _willCacheFor;
|
||||
|
@ -1519,8 +1519,8 @@ void MainWidget::onSharePhoneWithBot(PeerData *recipient) {
|
|||
onShareContact(recipient->id, App::self());
|
||||
}
|
||||
|
||||
void MainWidget::ui_showPeerHistoryAsync(quint64 peerId, qint32 showAtMsgId) {
|
||||
Ui::showPeerHistory(peerId, showAtMsgId);
|
||||
void MainWidget::ui_showPeerHistoryAsync(quint64 peerId, qint32 showAtMsgId, Ui::ShowWay way) {
|
||||
Ui::showPeerHistory(peerId, showAtMsgId, way);
|
||||
}
|
||||
|
||||
void MainWidget::ui_autoplayMediaInlineAsync(qint32 channelId, qint32 msgId) {
|
||||
|
@ -1532,13 +1532,18 @@ void MainWidget::ui_autoplayMediaInlineAsync(qint32 channelId, qint32 msgId) {
|
|||
}
|
||||
|
||||
void MainWidget::audioPlayProgress(const AudioMsgId &audioId) {
|
||||
if (audioId.type() == AudioMsgId::Type::Video) {
|
||||
audioPlayer()->videoSoundProgress(audioId);
|
||||
return;
|
||||
}
|
||||
|
||||
AudioMsgId playing;
|
||||
AudioPlayerState state = AudioPlayerStopped;
|
||||
audioPlayer()->currentState(&playing, &state);
|
||||
if (playing == audioId && state == AudioPlayerStoppedAtStart) {
|
||||
auto playbackState = audioPlayer()->currentState(&playing, audioId.type());
|
||||
if (playing == audioId && playbackState.state == AudioPlayerStoppedAtStart) {
|
||||
playbackState.state = AudioPlayerStopped;
|
||||
audioPlayer()->clearStoppedAtStart(audioId);
|
||||
|
||||
DocumentData *audio = audioId.audio;
|
||||
DocumentData *audio = audioId.audio();
|
||||
QString filepath = audio->filepath(DocumentData::FilePathResolveSaveFromData);
|
||||
if (!filepath.isEmpty()) {
|
||||
if (documentIsValidMediaFile(filepath)) {
|
||||
|
@ -1547,39 +1552,10 @@ void MainWidget::audioPlayProgress(const AudioMsgId &audioId) {
|
|||
}
|
||||
}
|
||||
|
||||
if (HistoryItem *item = App::histItemById(audioId.contextId)) {
|
||||
Ui::repaintHistoryItem(item);
|
||||
}
|
||||
if (auto items = InlineBots::Layout::documentItems()) {
|
||||
for (auto item : items->value(audioId.audio)) {
|
||||
Ui::repaintInlineItem(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (playing == audioId && audioId.type() == AudioMsgId::Type::Song) {
|
||||
_player->updateState(playing, playbackState);
|
||||
|
||||
void MainWidget::documentPlayProgress(const SongMsgId &songId) {
|
||||
SongMsgId playing;
|
||||
AudioPlayerState playingState = AudioPlayerStopped;
|
||||
int64 playingPosition = 0, playingDuration = 0;
|
||||
int32 playingFrequency = 0;
|
||||
audioPlayer()->currentState(&playing, &playingState, &playingPosition, &playingDuration, &playingFrequency);
|
||||
if (playing == songId && playingState == AudioPlayerStoppedAtStart) {
|
||||
playingState = AudioPlayerStopped;
|
||||
audioPlayer()->clearStoppedAtStart(songId);
|
||||
|
||||
DocumentData *document = songId.song;
|
||||
QString filepath = document->filepath(DocumentData::FilePathResolveSaveFromData);
|
||||
if (!filepath.isEmpty()) {
|
||||
if (documentIsValidMediaFile(filepath)) {
|
||||
psOpenFile(filepath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (playing == songId) {
|
||||
_player->updateState(playing, playingState, playingPosition, playingDuration, playingFrequency);
|
||||
|
||||
if (!(playingState & AudioPlayerStoppedMask) && playingState != AudioPlayerFinishing) {
|
||||
if (!(playbackState.state & AudioPlayerStoppedMask) && playbackState.state != AudioPlayerFinishing) {
|
||||
if (!_player->isOpened()) {
|
||||
_player->openPlayer();
|
||||
if (_player->isHidden() && !_a_show.animating()) {
|
||||
|
@ -1591,12 +1567,14 @@ void MainWidget::documentPlayProgress(const SongMsgId &songId) {
|
|||
}
|
||||
}
|
||||
|
||||
if (HistoryItem *item = App::histItemById(songId.contextId)) {
|
||||
Ui::repaintHistoryItem(item);
|
||||
}
|
||||
if (auto items = InlineBots::Layout::documentItems()) {
|
||||
for (auto item : items->value(songId.song)) {
|
||||
Ui::repaintInlineItem(item);
|
||||
if (audioId.type() != AudioMsgId::Type::Video) {
|
||||
if (auto item = App::histItemById(audioId.contextId())) {
|
||||
Ui::repaintHistoryItem(item);
|
||||
}
|
||||
if (auto items = InlineBots::Layout::documentItems()) {
|
||||
for (auto item : items->value(audioId.audio())) {
|
||||
Ui::repaintInlineItem(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1632,13 +1610,10 @@ void MainWidget::documentLoadProgress(FileLoader *loader) {
|
|||
App::wnd()->documentUpdated(document);
|
||||
|
||||
if (!document->loaded() && document->loading() && document->song() && audioPlayer()) {
|
||||
SongMsgId playing;
|
||||
AudioPlayerState playingState = AudioPlayerStopped;
|
||||
int64 playingPosition = 0, playingDuration = 0;
|
||||
int32 playingFrequency = 0;
|
||||
audioPlayer()->currentState(&playing, &playingState, &playingPosition, &playingDuration, &playingFrequency);
|
||||
if (playing.song == document && !_player->isHidden()) {
|
||||
_player->updateState(playing, playingState, playingPosition, playingDuration, playingFrequency);
|
||||
AudioMsgId playing;
|
||||
auto playbackState = audioPlayer()->currentState(&playing, AudioMsgId::Type::Song);
|
||||
if (playing.audio() == document && !_player->isHidden()) {
|
||||
_player->updateState(playing, playbackState);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2020,7 +1995,7 @@ void MainWidget::ctrlEnterSubmitUpdated() {
|
|||
_history->updateFieldSubmitSettings();
|
||||
}
|
||||
|
||||
void MainWidget::ui_showPeerHistory(quint64 peerId, qint32 showAtMsgId, bool back) {
|
||||
void MainWidget::ui_showPeerHistory(quint64 peerId, qint32 showAtMsgId, Ui::ShowWay way) {
|
||||
if (PeerData *peer = App::peerLoaded(peerId)) {
|
||||
if (peer->migrateTo()) {
|
||||
peer = peer->migrateTo();
|
||||
|
@ -2034,8 +2009,37 @@ void MainWidget::ui_showPeerHistory(quint64 peerId, qint32 showAtMsgId, bool bac
|
|||
return;
|
||||
}
|
||||
}
|
||||
if (!back && (!peerId || (_stack.size() == 1 && _stack[0]->type() == HistoryStackItem && _stack[0]->peer->id == peerId))) {
|
||||
back = true;
|
||||
|
||||
bool back = (way == Ui::ShowWay::Backward || !peerId);
|
||||
bool foundInStack = !peerId;
|
||||
if (foundInStack || (way == Ui::ShowWay::ClearStack)) {
|
||||
for_const (auto &item, _stack) {
|
||||
clearBotStartToken(item->peer);
|
||||
}
|
||||
_stack.clear();
|
||||
} else {
|
||||
for (int i = 0, s = _stack.size(); i < s; ++i) {
|
||||
if (_stack.at(i)->type() == HistoryStackItem && _stack.at(i)->peer->id == peerId) {
|
||||
foundInStack = true;
|
||||
while (_stack.size() > i) {
|
||||
clearBotStartToken(_stack.back()->peer);
|
||||
_stack.pop_back();
|
||||
}
|
||||
if (!back) {
|
||||
back = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (back || (way == Ui::ShowWay::ClearStack)) {
|
||||
dlgUpdated();
|
||||
_peerInStack = nullptr;
|
||||
_msgIdInStack = 0;
|
||||
dlgUpdated();
|
||||
} else {
|
||||
saveSectionInStack();
|
||||
}
|
||||
|
||||
PeerData *wasActivePeer = activePeer();
|
||||
|
@ -2047,10 +2051,12 @@ void MainWidget::ui_showPeerHistory(quint64 peerId, qint32 showAtMsgId, bool bac
|
|||
}
|
||||
|
||||
Window::SectionSlideParams animationParams;
|
||||
if (!_a_show.animating() && ((_history->isHidden() && (_wideSection || _overview)) || (Adaptive::OneColumn() && (_history->isHidden() || !peerId)))) {
|
||||
if (!_a_show.animating() && ((_history->isHidden() && (_wideSection || _overview)) || (Adaptive::OneColumn() && (_history->isHidden() || !peerId)) || back || (way == Ui::ShowWay::Forward))) {
|
||||
animationParams = prepareHistoryAnimation(peerId);
|
||||
}
|
||||
if (_history->peer() && _history->peer()->id != peerId) clearBotStartToken(_history->peer());
|
||||
if (_history->peer() && _history->peer()->id != peerId) {
|
||||
clearBotStartToken(_history->peer());
|
||||
}
|
||||
_history->showHistory(peerId, showAtMsgId);
|
||||
|
||||
bool noPeer = (!_history->peer() || !_history->peer()->id), onlyDialogs = noPeer && Adaptive::OneColumn();
|
||||
|
@ -2067,11 +2073,6 @@ void MainWidget::ui_showPeerHistory(quint64 peerId, qint32 showAtMsgId, bool bac
|
|||
_overview->rpcClear();
|
||||
_overview = nullptr;
|
||||
}
|
||||
clearBotStartToken(_peerInStack);
|
||||
dlgUpdated();
|
||||
_peerInStack = 0;
|
||||
_msgIdInStack = 0;
|
||||
_stack.clear();
|
||||
}
|
||||
if (onlyDialogs) {
|
||||
_topBar->hide();
|
||||
|
@ -2115,6 +2116,7 @@ void MainWidget::ui_showPeerHistory(quint64 peerId, qint32 showAtMsgId, bool bac
|
|||
}
|
||||
_dialogs->update();
|
||||
}
|
||||
topBar()->showAll();
|
||||
App::wnd()->getTitle()->updateBackButton();
|
||||
}
|
||||
|
||||
|
@ -2171,6 +2173,21 @@ bool MainWidget::mediaTypeSwitch() {
|
|||
return true;
|
||||
}
|
||||
|
||||
void MainWidget::saveSectionInStack() {
|
||||
if (_overview) {
|
||||
_stack.push_back(std_::make_unique<StackItemOverview>(_overview->peer(), _overview->type(), _overview->lastWidth(), _overview->lastScrollTop()));
|
||||
} else if (_wideSection) {
|
||||
_stack.push_back(std_::make_unique<StackItemSection>(_wideSection->createMemento()));
|
||||
} else if (_history->peer()) {
|
||||
dlgUpdated();
|
||||
_peerInStack = _history->peer();
|
||||
_msgIdInStack = _history->msgId();
|
||||
dlgUpdated();
|
||||
|
||||
_stack.push_back(std_::make_unique<StackItemHistory>(_peerInStack, _msgIdInStack, _history->replyReturns()));
|
||||
}
|
||||
}
|
||||
|
||||
void MainWidget::showMediaOverview(PeerData *peer, MediaOverviewType type, bool back, int32 lastScrollTop) {
|
||||
if (peer->migrateTo()) {
|
||||
peer = peer->migrateTo();
|
||||
|
@ -2191,17 +2208,7 @@ void MainWidget::showMediaOverview(PeerData *peer, MediaOverviewType type, bool
|
|||
animationParams = prepareOverviewAnimation();
|
||||
}
|
||||
if (!back) {
|
||||
if (_overview) {
|
||||
_stack.push_back(new StackItemOverview(_overview->peer(), _overview->type(), _overview->lastWidth(), _overview->lastScrollTop()));
|
||||
} else if (_wideSection) {
|
||||
_stack.push_back(new StackItemSection(_wideSection->createMemento()));
|
||||
} else if (_history->peer()) {
|
||||
dlgUpdated();
|
||||
_peerInStack = _history->peer();
|
||||
_msgIdInStack = _history->msgId();
|
||||
dlgUpdated();
|
||||
_stack.push_back(new StackItemHistory(_peerInStack, _msgIdInStack, _history->replyReturns()));
|
||||
}
|
||||
saveSectionInStack();
|
||||
}
|
||||
if (_overview) {
|
||||
_overview->hide();
|
||||
|
@ -2226,7 +2233,9 @@ void MainWidget::showMediaOverview(PeerData *peer, MediaOverviewType type, bool
|
|||
_overview->fastShow();
|
||||
}
|
||||
_history->animStop();
|
||||
if (back) clearBotStartToken(_history->peer());
|
||||
if (back) {
|
||||
clearBotStartToken(_history->peer());
|
||||
}
|
||||
_history->showHistory(0, 0);
|
||||
_history->hide();
|
||||
if (Adaptive::OneColumn()) _dialogs->hide();
|
||||
|
@ -2242,17 +2251,7 @@ void MainWidget::showWideSection(const Window::SectionMemento &memento) {
|
|||
return;
|
||||
}
|
||||
|
||||
if (_overview) {
|
||||
_stack.push_back(new StackItemOverview(_overview->peer(), _overview->type(), _overview->lastWidth(), _overview->lastScrollTop()));
|
||||
} else if (_wideSection) {
|
||||
_stack.push_back(new StackItemSection(_wideSection->createMemento()));
|
||||
} else if (_history->peer()) {
|
||||
dlgUpdated();
|
||||
_peerInStack = _history->peer();
|
||||
_msgIdInStack = _history->msgId();
|
||||
dlgUpdated();
|
||||
_stack.push_back(new StackItemHistory(_peerInStack, _msgIdInStack, _history->replyReturns()));
|
||||
}
|
||||
saveSectionInStack();
|
||||
showWideSectionAnimated(&memento, false);
|
||||
}
|
||||
|
||||
|
@ -2345,6 +2344,10 @@ void MainWidget::showWideSectionAnimated(const Window::SectionMemento *memento,
|
|||
App::wnd()->getTitle()->updateBackButton();
|
||||
}
|
||||
|
||||
bool MainWidget::stackIsEmpty() const {
|
||||
return _stack.isEmpty();
|
||||
}
|
||||
|
||||
void MainWidget::showBackFromStack() {
|
||||
if (selectingPeer()) return;
|
||||
if (_stack.isEmpty()) {
|
||||
|
@ -2352,34 +2355,33 @@ void MainWidget::showBackFromStack() {
|
|||
if (App::wnd()) QTimer::singleShot(0, App::wnd(), SLOT(setInnerFocus()));
|
||||
return;
|
||||
}
|
||||
StackItem *item = _stack.back();
|
||||
auto item = std_::move(_stack.back());
|
||||
_stack.pop_back();
|
||||
if (auto currentHistoryPeer = _history->peer()) {
|
||||
clearBotStartToken(currentHistoryPeer);
|
||||
}
|
||||
if (item->type() == HistoryStackItem) {
|
||||
dlgUpdated();
|
||||
_peerInStack = 0;
|
||||
_peerInStack = nullptr;
|
||||
_msgIdInStack = 0;
|
||||
for (int32 i = _stack.size(); i > 0;) {
|
||||
if (_stack.at(--i)->type() == HistoryStackItem) {
|
||||
_peerInStack = static_cast<StackItemHistory*>(_stack.at(i))->peer;
|
||||
_msgIdInStack = static_cast<StackItemHistory*>(_stack.at(i))->msgId;
|
||||
_peerInStack = static_cast<StackItemHistory*>(_stack.at(i).get())->peer;
|
||||
_msgIdInStack = static_cast<StackItemHistory*>(_stack.at(i).get())->msgId;
|
||||
dlgUpdated();
|
||||
break;
|
||||
}
|
||||
}
|
||||
StackItemHistory *histItem = static_cast<StackItemHistory*>(item);
|
||||
Ui::showPeerHistory(histItem->peer->id, App::main()->activeMsgId(), true);
|
||||
StackItemHistory *histItem = static_cast<StackItemHistory*>(item.get());
|
||||
Ui::showPeerHistory(histItem->peer->id, ShowAtUnreadMsgId, Ui::ShowWay::Backward);
|
||||
_history->setReplyReturns(histItem->peer->id, histItem->replyReturns);
|
||||
} else if (item->type() == SectionStackItem) {
|
||||
StackItemSection *sectionItem = static_cast<StackItemSection*>(item);
|
||||
StackItemSection *sectionItem = static_cast<StackItemSection*>(item.get());
|
||||
showWideSectionAnimated(sectionItem->memento(), true);
|
||||
} else if (item->type() == OverviewStackItem) {
|
||||
StackItemOverview *overItem = static_cast<StackItemOverview*>(item);
|
||||
StackItemOverview *overItem = static_cast<StackItemOverview*>(item.get());
|
||||
showMediaOverview(overItem->peer, overItem->mediaType, true, overItem->lastScrollTop);
|
||||
}
|
||||
delete item;
|
||||
}
|
||||
|
||||
void MainWidget::orderWidgets() {
|
||||
|
@ -3276,7 +3278,9 @@ void MainWidget::start(const MTPUser &user) {
|
|||
}
|
||||
_started = true;
|
||||
App::wnd()->sendServiceHistoryRequest();
|
||||
Local::readStickers();
|
||||
Local::readInstalledStickers();
|
||||
Local::readFeaturedStickers();
|
||||
Local::readRecentStickers();
|
||||
Local::readSavedGifs();
|
||||
_history->start();
|
||||
}
|
||||
|
@ -3287,51 +3291,50 @@ bool MainWidget::started() {
|
|||
|
||||
void MainWidget::openLocalUrl(const QString &url) {
|
||||
QString u(url.trimmed());
|
||||
if (u.startsWith(qstr("tg://resolve"), Qt::CaseInsensitive)) {
|
||||
QRegularExpressionMatch m = QRegularExpression(qsl("^tg://resolve/?\\?domain=([a-zA-Z0-9\\.\\_]+)(&|$)"), QRegularExpression::CaseInsensitiveOption).match(u);
|
||||
if (m.hasMatch()) {
|
||||
QString params = u.mid(m.capturedLength(0));
|
||||
if (u.size() > 8192) u = u.mid(0, 8192);
|
||||
|
||||
QString start, startToken;
|
||||
QRegularExpressionMatch startparam = QRegularExpression(qsl("(^|&)(start|startgroup)=([a-zA-Z0-9\\.\\_\\-]+)(&|$)")).match(params);
|
||||
if (startparam.hasMatch()) {
|
||||
start = startparam.captured(2);
|
||||
startToken = startparam.captured(3);
|
||||
}
|
||||
if (!u.startsWith(qstr("tg://"), Qt::CaseInsensitive)) {
|
||||
return;
|
||||
}
|
||||
|
||||
MsgId post = (start == qsl("startgroup")) ? ShowAtProfileMsgId : ShowAtUnreadMsgId;
|
||||
QRegularExpressionMatch postparam = QRegularExpression(qsl("(^|&)post=(\\d+)(&|$)")).match(params);
|
||||
if (postparam.hasMatch()) {
|
||||
post = postparam.captured(2).toInt();
|
||||
}
|
||||
|
||||
openPeerByName(m.captured(1), post, startToken);
|
||||
using namespace qthelp;
|
||||
auto matchOptions = RegExOption::CaseInsensitive;
|
||||
if (auto joinChatMatch = regex_match(qsl("^tg://join/?\\?invite=([a-zA-Z0-9\\.\\_\\-]+)(&|$)"), u, matchOptions)) {
|
||||
joinGroupByHash(joinChatMatch->captured(1));
|
||||
} else if (auto stickerSetMatch = regex_match(qsl("^tg://addstickers/?\\?set=([a-zA-Z0-9\\.\\_]+)(&|$)"), u, matchOptions)) {
|
||||
stickersBox(MTP_inputStickerSetShortName(MTP_string(stickerSetMatch->captured(1))));
|
||||
} else if (auto shareUrlMatch = regex_match(qsl("^tg://msg_url/?\\?(.+)(#|$)"), u, matchOptions)) {
|
||||
auto params = url_parse_params(shareUrlMatch->captured(1), UrlParamNameTransform::ToLower);
|
||||
auto url = params.value(qsl("url"));
|
||||
if (!url.isEmpty()) {
|
||||
shareUrlLayer(url, params.value("text"));
|
||||
}
|
||||
} else if (u.startsWith(qstr("tg://join"), Qt::CaseInsensitive)) {
|
||||
QRegularExpressionMatch m = QRegularExpression(qsl("^tg://join/?\\?invite=([a-zA-Z0-9\\.\\_\\-]+)(&|$)"), QRegularExpression::CaseInsensitiveOption).match(u);
|
||||
if (m.hasMatch()) {
|
||||
joinGroupByHash(m.captured(1));
|
||||
} else if (auto confirmPhoneMatch = regex_match(qsl("^tg://confirmphone/?\\?(.+)(#|$)"), u, matchOptions)) {
|
||||
auto params = url_parse_params(confirmPhoneMatch->captured(1), UrlParamNameTransform::ToLower);
|
||||
auto phone = params.value(qsl("phone"));
|
||||
auto hash = params.value(qsl("hash"));
|
||||
if (!phone.isEmpty() && !hash.isEmpty()) {
|
||||
ConfirmPhoneBox::start(phone, hash);
|
||||
}
|
||||
} else if (u.startsWith(qstr("tg://addstickers"), Qt::CaseInsensitive)) {
|
||||
QRegularExpressionMatch m = QRegularExpression(qsl("^tg://addstickers/?\\?set=([a-zA-Z0-9\\.\\_]+)(&|$)"), QRegularExpression::CaseInsensitiveOption).match(u);
|
||||
if (m.hasMatch()) {
|
||||
stickersBox(MTP_inputStickerSetShortName(MTP_string(m.captured(1))));
|
||||
}
|
||||
} else if (u.startsWith(qstr("tg://msg_url"), Qt::CaseInsensitive)) {
|
||||
QRegularExpressionMatch m = QRegularExpression(qsl("^tg://msg_url/?\\?(.+)(#|$)"), QRegularExpression::CaseInsensitiveOption).match(u);
|
||||
if (m.hasMatch()) {
|
||||
QStringList params = m.captured(1).split('&');
|
||||
QString url, text;
|
||||
for (int32 i = 0, l = params.size(); i < l; ++i) {
|
||||
if (params.at(i).startsWith(qstr("url="), Qt::CaseInsensitive)) {
|
||||
url = myUrlDecode(params.at(i).mid(4));
|
||||
} else if (params.at(i).startsWith(qstr("text="), Qt::CaseInsensitive)) {
|
||||
text = myUrlDecode(params.at(i).mid(5));
|
||||
} else if (auto usernameMatch = regex_match(qsl("^tg://resolve/?\\?(.+)(#|$)"), u, matchOptions)) {
|
||||
auto params = url_parse_params(usernameMatch->captured(1), UrlParamNameTransform::ToLower);
|
||||
auto domain = params.value(qsl("domain"));
|
||||
if (auto domainMatch = regex_match(qsl("^[a-zA-Z0-9\\.\\_]+$"), domain, matchOptions)) {
|
||||
auto start = qsl("start");
|
||||
auto startToken = params.value(start);
|
||||
if (startToken.isEmpty()) {
|
||||
start = qsl("startgroup");
|
||||
startToken = params.value(start);
|
||||
if (startToken.isEmpty()) {
|
||||
start = QString();
|
||||
}
|
||||
}
|
||||
if (!url.isEmpty()) {
|
||||
shareUrlLayer(url, text);
|
||||
auto post = (start == qsl("startgroup")) ? ShowAtProfileMsgId : ShowAtUnreadMsgId;
|
||||
auto postParam = params.value(qsl("post"));
|
||||
if (auto postId = postParam.toInt()) {
|
||||
post = postId;
|
||||
}
|
||||
openPeerByName(domain, post, startToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3347,7 +3350,7 @@ void MainWidget::openPeerByName(const QString &username, MsgId msgId, const QStr
|
|||
Ui::showLayer(new ContactsBox(peer->asUser()));
|
||||
} else if (peer->isUser() && peer->asUser()->botInfo) {
|
||||
// Always open bot chats, even from mention links.
|
||||
Ui::showPeerHistoryAsync(peer->id, ShowAtUnreadMsgId);
|
||||
Ui::showPeerHistoryAsync(peer->id, ShowAtUnreadMsgId, Ui::ShowWay::Forward);
|
||||
} else {
|
||||
Ui::showPeerProfile(peer);
|
||||
}
|
||||
|
@ -3362,7 +3365,7 @@ void MainWidget::openPeerByName(const QString &username, MsgId msgId, const QStr
|
|||
_history->resizeEvent(0);
|
||||
}
|
||||
}
|
||||
Ui::showPeerHistoryAsync(peer->id, msgId);
|
||||
Ui::showPeerHistoryAsync(peer->id, msgId, Ui::ShowWay::Forward);
|
||||
}
|
||||
} else {
|
||||
MTP::send(MTPcontacts_ResolveUsername(MTP_string(username)), rpcDone(&MainWidget::usernameResolveDone, qMakePair(msgId, startToken)), rpcFail(&MainWidget::usernameResolveFail, username));
|
||||
|
@ -3382,7 +3385,6 @@ void MainWidget::stickersBox(const MTPInputStickerSet &set) {
|
|||
}
|
||||
|
||||
void MainWidget::onStickersInstalled(uint64 setId) {
|
||||
emit stickersUpdated();
|
||||
_history->stickersInstalled(setId);
|
||||
}
|
||||
|
||||
|
@ -3430,7 +3432,7 @@ void MainWidget::usernameResolveDone(QPair<MsgId, QString> msgIdAndStartToken, c
|
|||
Ui::showLayer(new ContactsBox(peer->asUser()));
|
||||
} else if (peer->isUser() && peer->asUser()->botInfo) {
|
||||
// Always open bot chats, even from mention links.
|
||||
Ui::showPeerHistoryAsync(peer->id, ShowAtUnreadMsgId);
|
||||
Ui::showPeerHistoryAsync(peer->id, ShowAtUnreadMsgId, Ui::ShowWay::Forward);
|
||||
} else {
|
||||
Ui::showPeerProfile(peer);
|
||||
}
|
||||
|
@ -3461,11 +3463,21 @@ bool MainWidget::usernameResolveFail(QString name, const RPCError &error) {
|
|||
void MainWidget::inviteCheckDone(QString hash, const MTPChatInvite &invite) {
|
||||
switch (invite.type()) {
|
||||
case mtpc_chatInvite: {
|
||||
const auto &d(invite.c_chatInvite());
|
||||
ConfirmBox *box = new ConfirmBox(((d.is_channel() && !d.is_megagroup()) ? lng_group_invite_want_join_channel : lng_group_invite_want_join)(lt_title, qs(d.vtitle)), lang(lng_group_invite_join));
|
||||
auto &d(invite.c_chatInvite());
|
||||
|
||||
QVector<UserData*> participants;
|
||||
if (d.has_participants()) {
|
||||
auto &v = d.vparticipants.c_vector().v;
|
||||
participants.reserve(v.size());
|
||||
for_const (auto &user, v) {
|
||||
if (auto feededUser = App::feedUser(user)) {
|
||||
participants.push_back(feededUser);
|
||||
}
|
||||
}
|
||||
}
|
||||
auto box = std_::make_unique<ConfirmInviteBox>(qs(d.vtitle), d.vphoto, d.vparticipants_count.v, participants);
|
||||
_inviteHash = hash;
|
||||
connect(box, SIGNAL(confirmed()), this, SLOT(onInviteImport()));
|
||||
Ui::showLayer(box);
|
||||
Ui::showLayer(box.release());
|
||||
} break;
|
||||
|
||||
case mtpc_chatInviteAlready: {
|
||||
|
@ -3643,72 +3655,65 @@ void MainWidget::updateNotifySetting(PeerData *peer, NotifySettingStatus notify,
|
|||
|
||||
void MainWidget::incrementSticker(DocumentData *sticker) {
|
||||
if (!sticker || !sticker->sticker()) return;
|
||||
if (sticker->sticker()->set.type() == mtpc_inputStickerSetEmpty) return;
|
||||
|
||||
RecentStickerPack &recent(cGetRecentStickers());
|
||||
RecentStickerPack::iterator i = recent.begin(), e = recent.end();
|
||||
for (; i != e; ++i) {
|
||||
bool writeRecentStickers = false;
|
||||
auto &sets = Global::RefStickerSets();
|
||||
auto it = sets.find(Stickers::CloudRecentSetId);
|
||||
if (it == sets.cend()) {
|
||||
if (it == sets.cend()) {
|
||||
it = sets.insert(Stickers::CloudRecentSetId, Stickers::Set(Stickers::CloudRecentSetId, 0, lang(lng_recent_stickers), QString(), 0, 0, qFlags(MTPDstickerSet_ClientFlag::f_special)));
|
||||
} else {
|
||||
it->title = lang(lng_recent_stickers);
|
||||
}
|
||||
}
|
||||
auto index = it->stickers.indexOf(sticker);
|
||||
if (index > 0) {
|
||||
it->stickers.removeAt(index);
|
||||
}
|
||||
if (index) {
|
||||
it->stickers.push_front(sticker);
|
||||
writeRecentStickers = true;
|
||||
}
|
||||
|
||||
// Remove that sticker from old recent, now it is in cloud recent stickers.
|
||||
bool writeOldRecent = false;
|
||||
auto &recent = cGetRecentStickers();
|
||||
for (auto i = recent.begin(), e = recent.end(); i != e; ++i) {
|
||||
if (i->first == sticker) {
|
||||
i->second = recent.begin()->second; // throw to the first place
|
||||
//++i->second;
|
||||
//if (i->second > 0x8000) {
|
||||
// for (RecentStickerPack::iterator j = recent.begin(); j != e; ++j) {
|
||||
// if (j->second > 1) {
|
||||
// j->second /= 2;
|
||||
// } else {
|
||||
// j->second = 1;
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
for (; i != recent.begin(); --i) {
|
||||
if ((i - 1)->second > i->second) {
|
||||
break;
|
||||
}
|
||||
qSwap(*i, *(i - 1));
|
||||
}
|
||||
writeOldRecent = true;
|
||||
recent.erase(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i == e) {
|
||||
while (recent.size() >= StickerPanPerRow * StickerPanRowsPerPage) recent.pop_back();
|
||||
recent.push_front(qMakePair(sticker, recent.isEmpty() ? 1 : recent.begin()->second));
|
||||
//recent.push_back(qMakePair(sticker, 1));
|
||||
//for (i = recent.end() - 1; i != recent.begin(); --i) {
|
||||
// if ((i - 1)->second > i->second) {
|
||||
// break;
|
||||
// }
|
||||
// qSwap(*i, *(i - 1));
|
||||
//}
|
||||
while (!recent.isEmpty() && it->stickers.size() + recent.size() > Global::StickersRecentLimit()) {
|
||||
writeOldRecent = true;
|
||||
recent.pop_back();
|
||||
}
|
||||
|
||||
Local::writeUserSettings();
|
||||
|
||||
bool found = false;
|
||||
uint64 setId = 0;
|
||||
QString setName;
|
||||
switch (sticker->sticker()->set.type()) {
|
||||
case mtpc_inputStickerSetID: setId = sticker->sticker()->set.c_inputStickerSetID().vid.v; break;
|
||||
case mtpc_inputStickerSetShortName: setName = qs(sticker->sticker()->set.c_inputStickerSetShortName().vshort_name).toLower().trimmed(); break;
|
||||
if (writeOldRecent) {
|
||||
Local::writeUserSettings();
|
||||
}
|
||||
Stickers::Sets &sets(Global::RefStickerSets());
|
||||
for (auto i = sets.cbegin(); i != sets.cend(); ++i) {
|
||||
if (i->id == Stickers::CustomSetId || i->id == Stickers::DefaultSetId || (setId && i->id == setId) || (!setName.isEmpty() && i->shortName.toLower().trimmed() == setName)) {
|
||||
for (int32 j = 0, l = i->stickers.size(); j < l; ++j) {
|
||||
if (i->stickers.at(j) == sticker) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// Remove that sticker from custom stickers, now it is in cloud recent stickers.
|
||||
bool writeInstalledStickers = false;
|
||||
auto custom = sets.find(Stickers::CustomSetId);
|
||||
if (custom != sets.cend()) {
|
||||
int removeIndex = custom->stickers.indexOf(sticker);
|
||||
if (removeIndex >= 0) {
|
||||
custom->stickers.removeAt(removeIndex);
|
||||
if (custom->stickers.isEmpty()) {
|
||||
sets.erase(custom);
|
||||
}
|
||||
if (found) break;
|
||||
writeInstalledStickers = true;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
Stickers::Sets::iterator it = sets.find(Stickers::CustomSetId);
|
||||
if (it == sets.cend()) {
|
||||
it = sets.insert(Stickers::CustomSetId, Stickers::Set(Stickers::CustomSetId, 0, lang(lng_custom_stickers), QString(), 0, 0, 0));
|
||||
}
|
||||
it->stickers.push_back(sticker);
|
||||
++it->count;
|
||||
Local::writeStickers();
|
||||
|
||||
if (writeInstalledStickers) {
|
||||
Local::writeInstalledStickers();
|
||||
}
|
||||
if (writeRecentStickers) {
|
||||
Local::writeRecentStickers();
|
||||
}
|
||||
_history->updateRecentStickers();
|
||||
}
|
||||
|
@ -3767,7 +3772,10 @@ int32 MainWidget::dlgsWidth() const {
|
|||
MainWidget::~MainWidget() {
|
||||
if (App::main() == this) _history->showHistory(0, 0);
|
||||
|
||||
delete _hider;
|
||||
if (HistoryHider *hider = _hider) {
|
||||
_hider = nullptr;
|
||||
delete hider;
|
||||
}
|
||||
MTP::clearGlobalHandlers();
|
||||
|
||||
if (App::wnd()) App::wnd()->noMain(this);
|
||||
|
@ -4132,7 +4140,16 @@ void MainWidget::feedUpdate(const MTPUpdate &update) {
|
|||
|
||||
switch (update.type()) {
|
||||
case mtpc_updateNewMessage: {
|
||||
const auto &d(update.c_updateNewMessage());
|
||||
auto &d = update.c_updateNewMessage();
|
||||
|
||||
DataIsLoadedResult isDataLoaded = allDataLoadedForMessage(d.vmessage);
|
||||
if (!requestingDifference() && isDataLoaded != DataIsLoadedResult::Ok) {
|
||||
MTP_LOG(0, ("getDifference { good - after not all data loaded in updateNewMessage }%1").arg(cTestMode() ? " TESTMODE" : ""));
|
||||
|
||||
// This can be if this update was created by grouping
|
||||
// some short message update into an updates vector.
|
||||
return getDifference();
|
||||
}
|
||||
|
||||
if (!ptsUpdated(d.vpts.v, d.vpts_count.v, update)) {
|
||||
return;
|
||||
|
@ -4487,7 +4504,7 @@ void MainWidget::feedUpdate(const MTPUpdate &update) {
|
|||
} break;
|
||||
|
||||
case mtpc_updateNewChannelMessage: {
|
||||
const auto &d(update.c_updateNewChannelMessage());
|
||||
auto &d = update.c_updateNewChannelMessage();
|
||||
ChannelData *channel = App::channelLoaded(peerToChannel(peerFromMessage(d.vmessage)));
|
||||
DataIsLoadedResult isDataLoaded = allDataLoadedForMessage(d.vmessage);
|
||||
if (!requestingDifference() && (!channel || isDataLoaded != DataIsLoadedResult::Ok)) {
|
||||
|
@ -4641,16 +4658,23 @@ void MainWidget::feedUpdate(const MTPUpdate &update) {
|
|||
|
||||
////// Cloud sticker sets
|
||||
case mtpc_updateNewStickerSet: {
|
||||
const auto &d(update.c_updateNewStickerSet());
|
||||
auto &d = update.c_updateNewStickerSet();
|
||||
bool writeArchived = false;
|
||||
if (d.vstickerset.type() == mtpc_messages_stickerSet) {
|
||||
const auto &set(d.vstickerset.c_messages_stickerSet());
|
||||
auto &set = d.vstickerset.c_messages_stickerSet();
|
||||
if (set.vset.type() == mtpc_stickerSet) {
|
||||
const auto &s(set.vset.c_stickerSet());
|
||||
auto &s = set.vset.c_stickerSet();
|
||||
|
||||
Stickers::Sets &sets(Global::RefStickerSets());
|
||||
auto &sets = Global::RefStickerSets();
|
||||
auto it = sets.find(s.vid.v);
|
||||
if (it == sets.cend()) {
|
||||
it = sets.insert(s.vid.v, Stickers::Set(s.vid.v, s.vaccess_hash.v, stickerSetTitle(s), qs(s.vshort_name), s.vcount.v, s.vhash.v, s.vflags.v));
|
||||
it = sets.insert(s.vid.v, Stickers::Set(s.vid.v, s.vaccess_hash.v, stickerSetTitle(s), qs(s.vshort_name), s.vcount.v, s.vhash.v, s.vflags.v | MTPDstickerSet::Flag::f_installed));
|
||||
} else {
|
||||
it->flags |= MTPDstickerSet::Flag::f_installed;
|
||||
if (it->flags & MTPDstickerSet::Flag::f_archived) {
|
||||
it->flags &= ~MTPDstickerSet::Flag::f_archived;
|
||||
writeArchived = true;
|
||||
}
|
||||
}
|
||||
|
||||
const auto &v(set.vdocuments.c_vector().v);
|
||||
|
@ -4663,12 +4687,12 @@ void MainWidget::feedUpdate(const MTPUpdate &update) {
|
|||
it->stickers.push_back(doc);
|
||||
}
|
||||
it->emoji.clear();
|
||||
const auto &packs(set.vpacks.c_vector().v);
|
||||
auto &packs = set.vpacks.c_vector().v;
|
||||
for (int32 i = 0, l = packs.size(); i < l; ++i) {
|
||||
if (packs.at(i).type() != mtpc_stickerPack) continue;
|
||||
const auto &pack(packs.at(i).c_stickerPack());
|
||||
auto &pack = packs.at(i).c_stickerPack();
|
||||
if (EmojiPtr e = emojiGetNoColor(emojiFromText(qs(pack.vemoticon)))) {
|
||||
const auto &stickers(pack.vdocuments.c_vector().v);
|
||||
auto &stickers = pack.vdocuments.c_vector().v;
|
||||
StickerPack p;
|
||||
p.reserve(stickers.size());
|
||||
for (int32 j = 0, c = stickers.size(); j < c; ++j) {
|
||||
|
@ -4700,16 +4724,17 @@ void MainWidget::feedUpdate(const MTPUpdate &update) {
|
|||
sets.erase(custom);
|
||||
}
|
||||
}
|
||||
Local::writeStickers();
|
||||
Local::writeInstalledStickers();
|
||||
if (writeArchived) Local::writeArchivedStickers();
|
||||
emit stickersUpdated();
|
||||
}
|
||||
}
|
||||
} break;
|
||||
|
||||
case mtpc_updateStickerSetsOrder: {
|
||||
const auto &d(update.c_updateStickerSetsOrder());
|
||||
const auto &order(d.vorder.c_vector().v);
|
||||
const auto &sets(Global::StickerSets());
|
||||
auto &d = update.c_updateStickerSetsOrder();
|
||||
auto &order = d.vorder.c_vector().v;
|
||||
auto &sets = Global::StickerSets();
|
||||
Stickers::Order result;
|
||||
for (int32 i = 0, l = order.size(); i < l; ++i) {
|
||||
if (sets.constFind(order.at(i).v) == sets.cend()) {
|
||||
|
@ -4722,7 +4747,7 @@ void MainWidget::feedUpdate(const MTPUpdate &update) {
|
|||
App::main()->updateStickers();
|
||||
} else {
|
||||
Global::SetStickerSetsOrder(result);
|
||||
Local::writeStickers();
|
||||
Local::writeInstalledStickers();
|
||||
emit stickersUpdated();
|
||||
}
|
||||
} break;
|
||||
|
@ -4732,6 +4757,24 @@ void MainWidget::feedUpdate(const MTPUpdate &update) {
|
|||
App::main()->updateStickers();
|
||||
} break;
|
||||
|
||||
case mtpc_updateRecentStickers: {
|
||||
Global::SetLastStickersUpdate(0);
|
||||
App::main()->updateStickers();
|
||||
} break;
|
||||
|
||||
case mtpc_updateReadFeaturedStickers: {
|
||||
for (auto &set : Global::RefStickerSets()) {
|
||||
if (set.flags & MTPDstickerSet_ClientFlag::f_unread) {
|
||||
set.flags &= ~MTPDstickerSet_ClientFlag::f_unread;
|
||||
}
|
||||
}
|
||||
if (Global::FeaturedStickerSetsUnreadCount()) {
|
||||
Global::SetFeaturedStickerSetsUnreadCount(0);
|
||||
Local::writeFeaturedStickers();
|
||||
emit stickersUpdated();
|
||||
}
|
||||
} break;
|
||||
|
||||
////// Cloud saved GIFs
|
||||
case mtpc_updateSavedGifs: {
|
||||
cSetLastSavedGifsUpdate(0);
|
||||
|
|
|
@ -108,27 +108,6 @@ public:
|
|||
int32 lastWidth, lastScrollTop;
|
||||
};
|
||||
|
||||
class StackItems : public QVector<StackItem*> {
|
||||
public:
|
||||
bool contains(PeerData *peer) const {
|
||||
for (int32 i = 0, l = size(); i < l; ++i) {
|
||||
if (at(i)->peer == peer) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
void clear() {
|
||||
for (int32 i = 0, l = size(); i < l; ++i) {
|
||||
delete at(i);
|
||||
}
|
||||
QVector<StackItem*>::clear();
|
||||
}
|
||||
~StackItems() {
|
||||
clear();
|
||||
}
|
||||
};
|
||||
|
||||
enum SilentNotifiesStatus {
|
||||
SilentNotifiesDontChange,
|
||||
SilentNotifiesSetSilent,
|
||||
|
@ -224,6 +203,7 @@ public:
|
|||
bool mediaTypeSwitch();
|
||||
void showWideSection(const Window::SectionMemento &memento);
|
||||
void showMediaOverview(PeerData *peer, MediaOverviewType type, bool back = false, int32 lastScrollTop = -1);
|
||||
bool stackIsEmpty() const;
|
||||
void showBackFromStack();
|
||||
void orderWidgets();
|
||||
QRect historyRect() const;
|
||||
|
@ -404,7 +384,7 @@ public:
|
|||
void ui_repaintInlineItem(const InlineBots::Layout::ItemBase *layout);
|
||||
bool ui_isInlineItemVisible(const InlineBots::Layout::ItemBase *layout);
|
||||
bool ui_isInlineItemBeingChosen();
|
||||
void ui_showPeerHistory(quint64 peer, qint32 msgId, bool back);
|
||||
void ui_showPeerHistory(quint64 peer, qint32 msgId, Ui::ShowWay way);
|
||||
PeerData *ui_getPeerForMouseAction();
|
||||
|
||||
void notify_botCommandsChanged(UserData *bot);
|
||||
|
@ -421,9 +401,9 @@ public:
|
|||
void notify_historyMuteUpdated(History *history);
|
||||
void notify_handlePendingHistoryUpdate();
|
||||
|
||||
void cmd_search();
|
||||
void cmd_next_chat();
|
||||
void cmd_previous_chat();
|
||||
bool cmd_search();
|
||||
bool cmd_next_chat();
|
||||
bool cmd_previous_chat();
|
||||
|
||||
~MainWidget();
|
||||
|
||||
|
@ -445,7 +425,6 @@ public slots:
|
|||
void documentLoadProgress(FileLoader *loader);
|
||||
void documentLoadFailed(FileLoader *loader, bool started);
|
||||
void documentLoadRetry();
|
||||
void documentPlayProgress(const SongMsgId &songId);
|
||||
void inlineResultLoadProgress(FileLoader *loader);
|
||||
void inlineResultLoadFailed(FileLoader *loader, bool started);
|
||||
|
||||
|
@ -493,7 +472,7 @@ public slots:
|
|||
|
||||
void onSharePhoneWithBot(PeerData *recipient);
|
||||
|
||||
void ui_showPeerHistoryAsync(quint64 peerId, qint32 showAtMsgId);
|
||||
void ui_showPeerHistoryAsync(quint64 peerId, qint32 showAtMsgId, Ui::ShowWay way);
|
||||
void ui_autoplayMediaInlineAsync(qint32 channelId, qint32 msgId);
|
||||
|
||||
private slots:
|
||||
|
@ -519,6 +498,8 @@ private:
|
|||
Window::SectionSlideParams prepareOverviewAnimation();
|
||||
Window::SectionSlideParams prepareDialogsAnimation();
|
||||
|
||||
void saveSectionInStack();
|
||||
|
||||
bool _started = false;
|
||||
|
||||
uint64 failedObjId = 0;
|
||||
|
@ -598,7 +579,7 @@ private:
|
|||
ChildWidget<Window::TopBarWidget> _topBar;
|
||||
ConfirmBox *_forwardConfirm = nullptr; // for single column layout
|
||||
ChildWidget<HistoryHider> _hider = { nullptr };
|
||||
StackItems _stack;
|
||||
std_::vector_of_moveable<std_::unique_ptr<StackItem>> _stack;
|
||||
PeerData *_peerInStack = nullptr;
|
||||
MsgId _msgIdInStack = 0;
|
||||
|
||||
|
|
|
@ -174,7 +174,7 @@ void NotifyWindow::updateNotifyDisplay() {
|
|||
history->peer->loadUserpic(true, true);
|
||||
history->peer->paintUserpicLeft(p, st::notifyPhotoSize, st::notifyPhotoPos.x(), st::notifyPhotoPos.y(), width());
|
||||
} else {
|
||||
static QPixmap icon = QPixmap::fromImage(App::wnd()->iconLarge().scaled(st::notifyPhotoSize, st::notifyPhotoSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation), Qt::ColorOnly);
|
||||
static QPixmap icon = App::pixmapFromImageInPlace(App::wnd()->iconLarge().scaled(st::notifyPhotoSize, st::notifyPhotoSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
|
||||
p.drawPixmap(st::notifyPhotoPos.x(), st::notifyPhotoPos.y(), icon);
|
||||
}
|
||||
|
||||
|
@ -234,7 +234,7 @@ void NotifyWindow::updateNotifyDisplay() {
|
|||
}
|
||||
}
|
||||
|
||||
pm = QPixmap::fromImage(img, Qt::ColorOnly);
|
||||
pm = App::pixmapFromImageInPlace(std_::move(img));
|
||||
update();
|
||||
}
|
||||
|
||||
|
@ -246,7 +246,7 @@ void NotifyWindow::updatePeerPhoto() {
|
|||
p.drawPixmap(st::notifyPhotoPos.x(), st::notifyPhotoPos.y(), peerPhoto->pix(st::notifyPhotoSize));
|
||||
}
|
||||
peerPhoto = ImagePtr();
|
||||
pm = QPixmap::fromImage(img, Qt::ColorOnly);
|
||||
pm = App::pixmapFromImageInPlace(std_::move(img));
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
@ -1072,7 +1072,7 @@ bool MainWindow::eventFilter(QObject *obj, QEvent *e) {
|
|||
if (obj == Application::instance()) {
|
||||
QString url = static_cast<QFileOpenEvent*>(e)->url().toEncoded().trimmed();
|
||||
if (url.startsWith(qstr("tg://"), Qt::CaseInsensitive)) {
|
||||
cSetStartUrl(url);
|
||||
cSetStartUrl(url.mid(0, 8192));
|
||||
if (!cStartUrl().isEmpty() && App::main() && App::self()) {
|
||||
App::main()->openLocalUrl(cStartUrl());
|
||||
cSetStartUrl(QString());
|
||||
|
@ -1882,7 +1882,7 @@ QImage MainWindow::iconWithCounter(int size, int count, style::color bg, bool sm
|
|||
placeSmallCounter(img, size, count, bg, QPoint(), st::counterColor);
|
||||
} else {
|
||||
QPainter p(&img);
|
||||
p.drawPixmap(size / 2, size / 2, QPixmap::fromImage(iconWithCounter(-size / 2, count, bg, false), Qt::ColorOnly));
|
||||
p.drawPixmap(size / 2, size / 2, App::pixmapFromImageInPlace(iconWithCounter(-size / 2, count, bg, false)));
|
||||
}
|
||||
return img;
|
||||
}
|
||||
|
@ -1952,7 +1952,7 @@ PreLaunchWindow *PreLaunchWindowInstance = 0;
|
|||
PreLaunchWindow::PreLaunchWindow(QString title) : TWidget(0) {
|
||||
Fonts::start();
|
||||
|
||||
QIcon icon(QPixmap::fromImage(QImage(cPlatform() == dbipMac ? qsl(":/gui/art/iconbig256.png") : qsl(":/gui/art/icon256.png")), Qt::ColorOnly));
|
||||
QIcon icon(App::pixmapFromImageInPlace(QImage(cPlatform() == dbipMac ? qsl(":/gui/art/iconbig256.png") : qsl(":/gui/art/icon256.png"))));
|
||||
if (cPlatform() == dbipLinux32 || cPlatform() == dbipLinux64) {
|
||||
icon = QIcon::fromTheme("telegram", icon);
|
||||
}
|
||||
|
|
308
Telegram/SourceFiles/media/media_audio.h
Normal file
|
@ -0,0 +1,308 @@
|
|||
/*
|
||||
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 "core/basic_types.h"
|
||||
|
||||
void audioInit();
|
||||
bool audioWorks();
|
||||
void audioPlayNotify();
|
||||
void audioFinish();
|
||||
|
||||
enum AudioPlayerState {
|
||||
AudioPlayerStopped = 0x01,
|
||||
AudioPlayerStoppedAtEnd = 0x02,
|
||||
AudioPlayerStoppedAtError = 0x03,
|
||||
AudioPlayerStoppedAtStart = 0x04,
|
||||
AudioPlayerStoppedMask = 0x07,
|
||||
|
||||
AudioPlayerStarting = 0x08,
|
||||
AudioPlayerPlaying = 0x10,
|
||||
AudioPlayerFinishing = 0x18,
|
||||
AudioPlayerPausing = 0x20,
|
||||
AudioPlayerPaused = 0x28,
|
||||
AudioPlayerPausedAtEnd = 0x30,
|
||||
AudioPlayerResuming = 0x38,
|
||||
};
|
||||
|
||||
class AudioPlayerFader;
|
||||
class AudioPlayerLoaders;
|
||||
|
||||
struct VideoSoundData;
|
||||
struct VideoSoundPart;
|
||||
struct AudioPlaybackState {
|
||||
AudioPlayerState state = AudioPlayerStopped;
|
||||
int64 position = 0;
|
||||
int64 duration = 0;
|
||||
int32 frequency = 0;
|
||||
};
|
||||
|
||||
class AudioPlayer : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
AudioPlayer();
|
||||
|
||||
void play(const AudioMsgId &audio, int64 position = 0);
|
||||
void pauseresume(AudioMsgId::Type type, bool fast = false);
|
||||
void seek(int64 position); // type == AudioMsgId::Type::Song
|
||||
void stop(AudioMsgId::Type type);
|
||||
|
||||
// Video player audio stream interface.
|
||||
void initFromVideo(uint64 videoPlayId, std_::unique_ptr<VideoSoundData> &&data, int64 position);
|
||||
void feedFromVideo(VideoSoundPart &&part);
|
||||
int64 getVideoCorrectedTime(uint64 playId, int64 frameMs, uint64 systemMs);
|
||||
void videoSoundProgress(const AudioMsgId &audio);
|
||||
AudioPlaybackState currentVideoState(uint64 videoPlayId);
|
||||
void stopFromVideo(uint64 videoPlayId);
|
||||
void pauseFromVideo(uint64 videoPlayId);
|
||||
void resumeFromVideo(uint64 videoPlayId);
|
||||
|
||||
void stopAndClear();
|
||||
|
||||
AudioPlaybackState currentState(AudioMsgId *audio, AudioMsgId::Type type);
|
||||
|
||||
void clearStoppedAtStart(const AudioMsgId &audio);
|
||||
|
||||
void resumeDevice();
|
||||
|
||||
~AudioPlayer();
|
||||
|
||||
public slots:
|
||||
void onError(const AudioMsgId &audio);
|
||||
void onStopped(const AudioMsgId &audio);
|
||||
|
||||
signals:
|
||||
void updated(const AudioMsgId &audio);
|
||||
void stoppedOnError(const AudioMsgId &audio);
|
||||
void loaderOnStart(const AudioMsgId &audio, qint64 position);
|
||||
void loaderOnCancel(const AudioMsgId &audio);
|
||||
|
||||
void faderOnTimer();
|
||||
|
||||
void suppressSong();
|
||||
void unsuppressSong();
|
||||
void suppressAll();
|
||||
|
||||
void songVolumeChanged();
|
||||
void videoVolumeChanged();
|
||||
|
||||
private:
|
||||
bool fadedStop(AudioMsgId::Type type, bool *fadedStart = 0);
|
||||
bool updateCurrentStarted(AudioMsgId::Type type, int32 pos = -1);
|
||||
bool checkCurrentALError(AudioMsgId::Type type);
|
||||
|
||||
struct AudioMsg {
|
||||
void clear();
|
||||
|
||||
AudioMsgId audio;
|
||||
|
||||
FileLocation file;
|
||||
QByteArray data;
|
||||
AudioPlaybackState playbackState = defaultState();
|
||||
int64 skipStart = 0;
|
||||
int64 skipEnd = 0;
|
||||
bool loading = false;
|
||||
int64 started = 0;
|
||||
|
||||
uint32 source = 0;
|
||||
int32 nextBuffer = 0;
|
||||
uint32 buffers[3] = { 0 };
|
||||
int64 samplesCount[3] = { 0 };
|
||||
|
||||
uint64 videoPlayId = 0;
|
||||
std_::unique_ptr<VideoSoundData> videoData;
|
||||
|
||||
private:
|
||||
static AudioPlaybackState defaultState() {
|
||||
AudioPlaybackState result;
|
||||
result.frequency = AudioVoiceMsgFrequency;
|
||||
return result;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
void setStoppedState(AudioMsg *current, AudioPlayerState state = AudioPlayerStopped);
|
||||
|
||||
AudioMsg *dataForType(AudioMsgId::Type type, int index = -1); // -1 uses currentIndex(type)
|
||||
const AudioMsg *dataForType(AudioMsgId::Type type, int index = -1) const;
|
||||
int *currentIndex(AudioMsgId::Type type);
|
||||
const int *currentIndex(AudioMsgId::Type type) const;
|
||||
|
||||
int _audioCurrent;
|
||||
AudioMsg _audioData[AudioSimultaneousLimit];
|
||||
|
||||
int _songCurrent;
|
||||
AudioMsg _songData[AudioSimultaneousLimit];
|
||||
|
||||
AudioMsg _videoData;
|
||||
uint64 _lastVideoPlayId = 0;
|
||||
uint64 _lastVideoPlaybackWhen = 0;
|
||||
uint64 _lastVideoPlaybackCorrectedMs = 0;
|
||||
QMutex _lastVideoMutex;
|
||||
|
||||
QMutex _mutex;
|
||||
|
||||
friend class AudioPlayerFader;
|
||||
friend class AudioPlayerLoaders;
|
||||
|
||||
QThread _faderThread, _loaderThread;
|
||||
AudioPlayerFader *_fader;
|
||||
AudioPlayerLoaders *_loader;
|
||||
|
||||
};
|
||||
|
||||
namespace internal {
|
||||
|
||||
QMutex *audioPlayerMutex();
|
||||
float64 audioSuppressGain();
|
||||
float64 audioSuppressSongGain();
|
||||
bool audioCheckError();
|
||||
|
||||
} // namespace internal
|
||||
|
||||
class AudioCaptureInner;
|
||||
|
||||
class AudioCapture : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
AudioCapture();
|
||||
|
||||
void start();
|
||||
void stop(bool needResult);
|
||||
|
||||
bool check();
|
||||
|
||||
~AudioCapture();
|
||||
|
||||
signals:
|
||||
|
||||
void captureOnStart();
|
||||
void captureOnStop(bool needResult);
|
||||
|
||||
void onDone(QByteArray data, VoiceWaveform waveform, qint32 samples);
|
||||
void onUpdate(quint16 level, qint32 samples);
|
||||
void onError();
|
||||
|
||||
private:
|
||||
|
||||
friend class AudioCaptureInner;
|
||||
|
||||
QThread _captureThread;
|
||||
AudioCaptureInner *_capture;
|
||||
|
||||
};
|
||||
|
||||
AudioPlayer *audioPlayer();
|
||||
AudioCapture *audioCapture();
|
||||
|
||||
class AudioPlayerFader : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
AudioPlayerFader(QThread *thread);
|
||||
void resumeDevice();
|
||||
|
||||
signals:
|
||||
void error(const AudioMsgId &audio);
|
||||
void playPositionUpdated(const AudioMsgId &audio);
|
||||
void audioStopped(const AudioMsgId &audio);
|
||||
void needToPreload(const AudioMsgId &audio);
|
||||
|
||||
void stopPauseDevice();
|
||||
|
||||
public slots:
|
||||
void onInit();
|
||||
void onTimer();
|
||||
void onPauseTimer();
|
||||
void onPauseTimerStop();
|
||||
|
||||
void onSuppressSong();
|
||||
void onUnsuppressSong();
|
||||
void onSuppressAll();
|
||||
void onSongVolumeChanged();
|
||||
void onVideoVolumeChanged();
|
||||
|
||||
private:
|
||||
enum {
|
||||
EmitError = 0x01,
|
||||
EmitStopped = 0x02,
|
||||
EmitPositionUpdated = 0x04,
|
||||
EmitNeedToPreload = 0x08,
|
||||
};
|
||||
int32 updateOnePlayback(AudioPlayer::AudioMsg *m, bool &hasPlaying, bool &hasFading, float64 suppressGain, bool suppressGainChanged);
|
||||
void setStoppedState(AudioPlayer::AudioMsg *m, AudioPlayerState state = AudioPlayerStopped);
|
||||
|
||||
QTimer _timer, _pauseTimer;
|
||||
QMutex _pauseMutex;
|
||||
bool _pauseFlag, _paused;
|
||||
|
||||
bool _suppressAll, _suppressAllAnim, _suppressSong, _suppressSongAnim, _songVolumeChanged, _videoVolumeChanged;
|
||||
anim::fvalue _suppressAllGain, _suppressSongGain;
|
||||
uint64 _suppressAllStart, _suppressSongStart;
|
||||
|
||||
};
|
||||
|
||||
struct AudioCapturePrivate;
|
||||
struct AVFrame;
|
||||
|
||||
class AudioCaptureInner : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
AudioCaptureInner(QThread *thread);
|
||||
~AudioCaptureInner();
|
||||
|
||||
signals:
|
||||
|
||||
void error();
|
||||
void update(quint16 level, qint32 samples);
|
||||
void done(QByteArray data, VoiceWaveform waveform, qint32 samples);
|
||||
|
||||
public slots:
|
||||
|
||||
void onInit();
|
||||
void onStart();
|
||||
void onStop(bool needResult);
|
||||
|
||||
void onTimeout();
|
||||
|
||||
private:
|
||||
|
||||
void processFrame(int32 offset, int32 framesize);
|
||||
|
||||
void writeFrame(AVFrame *frame);
|
||||
|
||||
// Writes the packets till EAGAIN is got from av_receive_packet()
|
||||
// Returns number of packets written or -1 on error
|
||||
int writePackets();
|
||||
|
||||
AudioCapturePrivate *d;
|
||||
QTimer _timer;
|
||||
QByteArray _captured;
|
||||
|
||||
};
|
||||
|
||||
MTPDocumentAttribute audioReadSongAttributes(const QString &fname, const QByteArray &data, QImage &cover, QByteArray &coverBytes, QByteArray &coverFormat);
|
||||
VoiceWaveform audioCountWaveform(const FileLocation &file, const QByteArray &data);
|
329
Telegram/SourceFiles/media/media_audio_ffmpeg_loader.cpp
Normal file
|
@ -0,0 +1,329 @@
|
|||
/*
|
||||
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 "media/media_audio_ffmpeg_loader.h"
|
||||
|
||||
constexpr AVSampleFormat AudioToFormat = AV_SAMPLE_FMT_S16;
|
||||
constexpr int64_t AudioToChannelLayout = AV_CH_LAYOUT_STEREO;
|
||||
constexpr int32 AudioToChannels = 2;
|
||||
|
||||
bool AbstractFFMpegLoader::open(qint64 &position) {
|
||||
if (!AudioPlayerLoader::openFile()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int res = 0;
|
||||
char err[AV_ERROR_MAX_STRING_SIZE] = { 0 };
|
||||
|
||||
ioBuffer = (uchar*)av_malloc(AVBlockSize);
|
||||
if (data.isEmpty()) {
|
||||
ioContext = avio_alloc_context(ioBuffer, AVBlockSize, 0, reinterpret_cast<void*>(this), &AbstractFFMpegLoader::_read_file, 0, &AbstractFFMpegLoader::_seek_file);
|
||||
} else {
|
||||
ioContext = avio_alloc_context(ioBuffer, AVBlockSize, 0, reinterpret_cast<void*>(this), &AbstractFFMpegLoader::_read_data, 0, &AbstractFFMpegLoader::_seek_data);
|
||||
}
|
||||
fmtContext = avformat_alloc_context();
|
||||
if (!fmtContext) {
|
||||
DEBUG_LOG(("Audio Read Error: Unable to avformat_alloc_context for file '%1', data size '%2'").arg(file.name()).arg(data.size()));
|
||||
return false;
|
||||
}
|
||||
fmtContext->pb = ioContext;
|
||||
|
||||
if ((res = avformat_open_input(&fmtContext, 0, 0, 0)) < 0) {
|
||||
ioBuffer = 0;
|
||||
|
||||
DEBUG_LOG(("Audio Read Error: Unable to avformat_open_input for file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res)));
|
||||
return false;
|
||||
}
|
||||
_opened = true;
|
||||
|
||||
if ((res = avformat_find_stream_info(fmtContext, 0)) < 0) {
|
||||
DEBUG_LOG(("Audio Read Error: Unable to avformat_find_stream_info for file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res)));
|
||||
return false;
|
||||
}
|
||||
|
||||
streamId = av_find_best_stream(fmtContext, AVMEDIA_TYPE_AUDIO, -1, -1, &codec, 0);
|
||||
if (streamId < 0) {
|
||||
LOG(("Audio Error: Unable to av_find_best_stream for file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(streamId).arg(av_make_error_string(err, sizeof(err), streamId)));
|
||||
return false;
|
||||
}
|
||||
|
||||
freq = fmtContext->streams[streamId]->codecpar->sample_rate;
|
||||
if (fmtContext->streams[streamId]->duration == AV_NOPTS_VALUE) {
|
||||
len = (fmtContext->duration * freq) / AV_TIME_BASE;
|
||||
} else {
|
||||
len = (fmtContext->streams[streamId]->duration * freq * fmtContext->streams[streamId]->time_base.num) / fmtContext->streams[streamId]->time_base.den;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
AbstractFFMpegLoader::~AbstractFFMpegLoader() {
|
||||
if (_opened) {
|
||||
avformat_close_input(&fmtContext);
|
||||
}
|
||||
if (ioContext) {
|
||||
av_free(ioContext->buffer);
|
||||
av_free(ioContext);
|
||||
} else if (ioBuffer) {
|
||||
av_free(ioBuffer);
|
||||
}
|
||||
if (fmtContext) avformat_free_context(fmtContext);
|
||||
}
|
||||
|
||||
int AbstractFFMpegLoader::_read_data(void *opaque, uint8_t *buf, int buf_size) {
|
||||
AbstractFFMpegLoader *l = reinterpret_cast<AbstractFFMpegLoader*>(opaque);
|
||||
|
||||
int32 nbytes = qMin(l->data.size() - l->dataPos, int32(buf_size));
|
||||
if (nbytes <= 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
memcpy(buf, l->data.constData() + l->dataPos, nbytes);
|
||||
l->dataPos += nbytes;
|
||||
return nbytes;
|
||||
}
|
||||
|
||||
int64_t AbstractFFMpegLoader::_seek_data(void *opaque, int64_t offset, int whence) {
|
||||
AbstractFFMpegLoader *l = reinterpret_cast<AbstractFFMpegLoader*>(opaque);
|
||||
|
||||
int32 newPos = -1;
|
||||
switch (whence) {
|
||||
case SEEK_SET: newPos = offset; break;
|
||||
case SEEK_CUR: newPos = l->dataPos + offset; break;
|
||||
case SEEK_END: newPos = l->data.size() + offset; break;
|
||||
}
|
||||
if (newPos < 0 || newPos > l->data.size()) {
|
||||
return -1;
|
||||
}
|
||||
l->dataPos = newPos;
|
||||
return l->dataPos;
|
||||
}
|
||||
|
||||
int AbstractFFMpegLoader::_read_file(void *opaque, uint8_t *buf, int buf_size) {
|
||||
AbstractFFMpegLoader *l = reinterpret_cast<AbstractFFMpegLoader*>(opaque);
|
||||
return int(l->f.read((char*)(buf), buf_size));
|
||||
}
|
||||
|
||||
int64_t AbstractFFMpegLoader::_seek_file(void *opaque, int64_t offset, int whence) {
|
||||
AbstractFFMpegLoader *l = reinterpret_cast<AbstractFFMpegLoader*>(opaque);
|
||||
|
||||
switch (whence) {
|
||||
case SEEK_SET: return l->f.seek(offset) ? l->f.pos() : -1;
|
||||
case SEEK_CUR: return l->f.seek(l->f.pos() + offset) ? l->f.pos() : -1;
|
||||
case SEEK_END: return l->f.seek(l->f.size() + offset) ? l->f.pos() : -1;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
FFMpegLoader::FFMpegLoader(const FileLocation &file, const QByteArray &data) : AbstractFFMpegLoader(file, data) {
|
||||
frame = av_frame_alloc();
|
||||
}
|
||||
|
||||
bool FFMpegLoader::open(qint64 &position) {
|
||||
if (!AbstractFFMpegLoader::open(position)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int res = 0;
|
||||
char err[AV_ERROR_MAX_STRING_SIZE] = { 0 };
|
||||
|
||||
auto codecParams = fmtContext->streams[streamId]->codecpar;
|
||||
|
||||
codecContext = avcodec_alloc_context3(nullptr);
|
||||
if (!codecContext) {
|
||||
LOG(("Audio Error: Unable to avcodec_alloc_context3 for file '%1', data size '%2'").arg(file.name()).arg(data.size()));
|
||||
return false;
|
||||
}
|
||||
if ((res = avcodec_parameters_to_context(codecContext, codecParams)) < 0) {
|
||||
LOG(("Audio Error: Unable to avcodec_parameters_to_context for file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res)));
|
||||
return false;
|
||||
}
|
||||
av_codec_set_pkt_timebase(codecContext, fmtContext->streams[streamId]->time_base);
|
||||
av_opt_set_int(codecContext, "refcounted_frames", 1, 0);
|
||||
|
||||
if ((res = avcodec_open2(codecContext, codec, 0)) < 0) {
|
||||
LOG(("Audio Error: Unable to avcodec_open2 for file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res)));
|
||||
return false;
|
||||
}
|
||||
|
||||
uint64_t layout = codecParams->channel_layout;
|
||||
inputFormat = codecContext->sample_fmt;
|
||||
switch (layout) {
|
||||
case AV_CH_LAYOUT_MONO:
|
||||
switch (inputFormat) {
|
||||
case AV_SAMPLE_FMT_U8:
|
||||
case AV_SAMPLE_FMT_U8P: fmt = AL_FORMAT_MONO8; sampleSize = 1; break;
|
||||
case AV_SAMPLE_FMT_S16:
|
||||
case AV_SAMPLE_FMT_S16P: fmt = AL_FORMAT_MONO16; sampleSize = sizeof(uint16); break;
|
||||
default:
|
||||
sampleSize = -1; // convert needed
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case AV_CH_LAYOUT_STEREO:
|
||||
switch (inputFormat) {
|
||||
case AV_SAMPLE_FMT_U8: fmt = AL_FORMAT_STEREO8; sampleSize = 2; break;
|
||||
case AV_SAMPLE_FMT_S16: fmt = AL_FORMAT_STEREO16; sampleSize = 2 * sizeof(uint16); break;
|
||||
default:
|
||||
sampleSize = -1; // convert needed
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
sampleSize = -1; // convert needed
|
||||
break;
|
||||
}
|
||||
if (freq != 44100 && freq != 48000) {
|
||||
sampleSize = -1; // convert needed
|
||||
}
|
||||
|
||||
if (sampleSize < 0) {
|
||||
swrContext = swr_alloc();
|
||||
if (!swrContext) {
|
||||
LOG(("Audio Error: Unable to swr_alloc for file '%1', data size '%2'").arg(file.name()).arg(data.size()));
|
||||
return false;
|
||||
}
|
||||
int64_t src_ch_layout = layout, dst_ch_layout = AudioToChannelLayout;
|
||||
srcRate = freq;
|
||||
AVSampleFormat src_sample_fmt = inputFormat, dst_sample_fmt = AudioToFormat;
|
||||
dstRate = (freq != 44100 && freq != 48000) ? AudioVoiceMsgFrequency : freq;
|
||||
|
||||
av_opt_set_int(swrContext, "in_channel_layout", src_ch_layout, 0);
|
||||
av_opt_set_int(swrContext, "in_sample_rate", srcRate, 0);
|
||||
av_opt_set_sample_fmt(swrContext, "in_sample_fmt", src_sample_fmt, 0);
|
||||
av_opt_set_int(swrContext, "out_channel_layout", dst_ch_layout, 0);
|
||||
av_opt_set_int(swrContext, "out_sample_rate", dstRate, 0);
|
||||
av_opt_set_sample_fmt(swrContext, "out_sample_fmt", dst_sample_fmt, 0);
|
||||
|
||||
if ((res = swr_init(swrContext)) < 0) {
|
||||
LOG(("Audio Error: Unable to swr_init for file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res)));
|
||||
return false;
|
||||
}
|
||||
|
||||
sampleSize = AudioToChannels * sizeof(short);
|
||||
freq = dstRate;
|
||||
len = av_rescale_rnd(len, dstRate, srcRate, AV_ROUND_UP);
|
||||
position = av_rescale_rnd(position, dstRate, srcRate, AV_ROUND_DOWN);
|
||||
fmt = AL_FORMAT_STEREO16;
|
||||
|
||||
maxResampleSamples = av_rescale_rnd(AVBlockSize / sampleSize, dstRate, srcRate, AV_ROUND_UP);
|
||||
if ((res = av_samples_alloc_array_and_samples(&dstSamplesData, 0, AudioToChannels, maxResampleSamples, AudioToFormat, 0)) < 0) {
|
||||
LOG(("Audio Error: Unable to av_samples_alloc for file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res)));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (position) {
|
||||
int64 ts = (position * fmtContext->streams[streamId]->time_base.den) / (freq * fmtContext->streams[streamId]->time_base.num);
|
||||
if (av_seek_frame(fmtContext, streamId, ts, AVSEEK_FLAG_ANY) < 0) {
|
||||
if (av_seek_frame(fmtContext, streamId, ts, 0) < 0) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
AudioPlayerLoader::ReadResult FFMpegLoader::readMore(QByteArray &result, int64 &samplesAdded) {
|
||||
int res;
|
||||
|
||||
av_frame_unref(frame);
|
||||
res = avcodec_receive_frame(codecContext, frame);
|
||||
if (res >= 0) {
|
||||
return readFromReadyFrame(result, samplesAdded);
|
||||
}
|
||||
|
||||
if (res == AVERROR_EOF) {
|
||||
return ReadResult::EndOfFile;
|
||||
} else if (res != AVERROR(EAGAIN)) {
|
||||
char err[AV_ERROR_MAX_STRING_SIZE] = { 0 };
|
||||
LOG(("Audio Error: Unable to avcodec_receive_frame() file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res)));
|
||||
return ReadResult::Error;
|
||||
}
|
||||
|
||||
if ((res = av_read_frame(fmtContext, &avpkt)) < 0) {
|
||||
if (res != AVERROR_EOF) {
|
||||
char err[AV_ERROR_MAX_STRING_SIZE] = { 0 };
|
||||
LOG(("Audio Error: Unable to av_read_frame() file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res)));
|
||||
return ReadResult::Error;
|
||||
}
|
||||
avcodec_send_packet(codecContext, nullptr); // drain
|
||||
return ReadResult::Ok;
|
||||
}
|
||||
|
||||
if (avpkt.stream_index == streamId) {
|
||||
res = avcodec_send_packet(codecContext, &avpkt);
|
||||
if (res < 0) {
|
||||
av_packet_unref(&avpkt);
|
||||
|
||||
char err[AV_ERROR_MAX_STRING_SIZE] = { 0 };
|
||||
LOG(("Audio Error: Unable to avcodec_send_packet() file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res)));
|
||||
if (res == AVERROR_INVALIDDATA) {
|
||||
return ReadResult::NotYet; // try to skip bad packet
|
||||
}
|
||||
return ReadResult::Error;
|
||||
}
|
||||
}
|
||||
av_packet_unref(&avpkt);
|
||||
return ReadResult::Ok;
|
||||
}
|
||||
|
||||
AudioPlayerLoader::ReadResult FFMpegLoader::readFromReadyFrame(QByteArray &result, int64 &samplesAdded) {
|
||||
int res = 0;
|
||||
|
||||
if (dstSamplesData) { // convert needed
|
||||
int64_t dstSamples = av_rescale_rnd(swr_get_delay(swrContext, srcRate) + frame->nb_samples, dstRate, srcRate, AV_ROUND_UP);
|
||||
if (dstSamples > maxResampleSamples) {
|
||||
maxResampleSamples = dstSamples;
|
||||
av_free(dstSamplesData[0]);
|
||||
|
||||
if ((res = av_samples_alloc(dstSamplesData, 0, AudioToChannels, maxResampleSamples, AudioToFormat, 1)) < 0) {
|
||||
dstSamplesData[0] = 0;
|
||||
char err[AV_ERROR_MAX_STRING_SIZE] = { 0 };
|
||||
LOG(("Audio Error: Unable to av_samples_alloc for file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res)));
|
||||
return ReadResult::Error;
|
||||
}
|
||||
}
|
||||
if ((res = swr_convert(swrContext, dstSamplesData, dstSamples, (const uint8_t**)frame->extended_data, frame->nb_samples)) < 0) {
|
||||
char err[AV_ERROR_MAX_STRING_SIZE] = { 0 };
|
||||
LOG(("Audio Error: Unable to swr_convert for file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res)));
|
||||
return ReadResult::Error;
|
||||
}
|
||||
int32 resultLen = av_samples_get_buffer_size(0, AudioToChannels, res, AudioToFormat, 1);
|
||||
result.append((const char*)dstSamplesData[0], resultLen);
|
||||
samplesAdded += resultLen / sampleSize;
|
||||
} else {
|
||||
result.append((const char*)frame->extended_data[0], frame->nb_samples * sampleSize);
|
||||
samplesAdded += frame->nb_samples;
|
||||
}
|
||||
return ReadResult::Ok;
|
||||
}
|
||||
|
||||
FFMpegLoader::~FFMpegLoader() {
|
||||
if (codecContext) avcodec_free_context(&codecContext);
|
||||
if (swrContext) swr_free(&swrContext);
|
||||
if (dstSamplesData) {
|
||||
if (dstSamplesData[0]) {
|
||||
av_freep(&dstSamplesData[0]);
|
||||
}
|
||||
av_freep(&dstSamplesData);
|
||||
}
|
||||
av_frame_free(&frame);
|
||||
}
|
104
Telegram/SourceFiles/media/media_audio_ffmpeg_loader.h
Normal file
|
@ -0,0 +1,104 @@
|
|||
/*
|
||||
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 "media/media_audio_loader.h"
|
||||
|
||||
extern "C" {
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libavutil/opt.h>
|
||||
#include <libswresample/swresample.h>
|
||||
} // extern "C"
|
||||
|
||||
#include <AL/al.h>
|
||||
|
||||
class AbstractFFMpegLoader : public AudioPlayerLoader {
|
||||
public:
|
||||
AbstractFFMpegLoader(const FileLocation &file, const QByteArray &data) : AudioPlayerLoader(file, data) {
|
||||
}
|
||||
|
||||
bool open(qint64 &position) override;
|
||||
|
||||
int64 duration() override {
|
||||
return len;
|
||||
}
|
||||
|
||||
int32 frequency() override {
|
||||
return freq;
|
||||
}
|
||||
|
||||
~AbstractFFMpegLoader();
|
||||
|
||||
protected:
|
||||
int32 freq = AudioVoiceMsgFrequency;
|
||||
int64 len = 0;
|
||||
|
||||
uchar *ioBuffer = nullptr;
|
||||
AVIOContext *ioContext = nullptr;
|
||||
AVFormatContext *fmtContext = nullptr;
|
||||
AVCodec *codec = nullptr;
|
||||
int32 streamId = 0;
|
||||
|
||||
bool _opened = false;
|
||||
|
||||
private:
|
||||
static int _read_data(void *opaque, uint8_t *buf, int buf_size);
|
||||
static int64_t _seek_data(void *opaque, int64_t offset, int whence);
|
||||
static int _read_file(void *opaque, uint8_t *buf, int buf_size);
|
||||
static int64_t _seek_file(void *opaque, int64_t offset, int whence);
|
||||
|
||||
};
|
||||
|
||||
class FFMpegLoader : public AbstractFFMpegLoader {
|
||||
public:
|
||||
FFMpegLoader(const FileLocation &file, const QByteArray &data);
|
||||
|
||||
bool open(qint64 &position) override;
|
||||
|
||||
int32 format() override {
|
||||
return fmt;
|
||||
}
|
||||
|
||||
ReadResult readMore(QByteArray &result, int64 &samplesAdded) override;
|
||||
|
||||
~FFMpegLoader();
|
||||
|
||||
protected:
|
||||
int32 sampleSize = 2 * sizeof(uint16);
|
||||
|
||||
private:
|
||||
ReadResult readFromReadyFrame(QByteArray &result, int64 &samplesAdded);
|
||||
|
||||
int32 fmt = AL_FORMAT_STEREO16;
|
||||
int32 srcRate = AudioVoiceMsgFrequency;
|
||||
int32 dstRate = AudioVoiceMsgFrequency;
|
||||
int32 maxResampleSamples = 1024;
|
||||
uint8_t **dstSamplesData = nullptr;
|
||||
|
||||
AVCodecContext *codecContext = nullptr;
|
||||
AVPacket avpkt;
|
||||
AVSampleFormat inputFormat;
|
||||
AVFrame *frame = nullptr;
|
||||
|
||||
SwrContext *swrContext = nullptr;
|
||||
|
||||
};
|
80
Telegram/SourceFiles/media/media_audio_loader.cpp
Normal file
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
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 "media/media_audio_loader.h"
|
||||
|
||||
AudioPlayerLoader::AudioPlayerLoader(const FileLocation &file, const QByteArray &data)
|
||||
: file(file)
|
||||
, data(data) {
|
||||
}
|
||||
|
||||
AudioPlayerLoader::~AudioPlayerLoader() {
|
||||
if (access) {
|
||||
file.accessDisable();
|
||||
access = false;
|
||||
}
|
||||
}
|
||||
|
||||
bool AudioPlayerLoader::check(const FileLocation &file, const QByteArray &data) {
|
||||
return this->file == file && this->data.size() == data.size();
|
||||
}
|
||||
|
||||
void AudioPlayerLoader::saveDecodedSamples(QByteArray *samples, int64 *samplesCount) {
|
||||
t_assert(_savedSamplesCount == 0);
|
||||
t_assert(_savedSamples.isEmpty());
|
||||
t_assert(!_holdsSavedSamples);
|
||||
samples->swap(_savedSamples);
|
||||
std::swap(*samplesCount, _savedSamplesCount);
|
||||
_holdsSavedSamples = true;
|
||||
}
|
||||
|
||||
void AudioPlayerLoader::takeSavedDecodedSamples(QByteArray *samples, int64 *samplesCount) {
|
||||
t_assert(*samplesCount == 0);
|
||||
t_assert(samples->isEmpty());
|
||||
t_assert(_holdsSavedSamples);
|
||||
samples->swap(_savedSamples);
|
||||
std::swap(*samplesCount, _savedSamplesCount);
|
||||
_holdsSavedSamples = false;
|
||||
}
|
||||
|
||||
bool AudioPlayerLoader::holdsSavedDecodedSamples() const {
|
||||
return _holdsSavedSamples;
|
||||
}
|
||||
|
||||
bool AudioPlayerLoader::openFile() {
|
||||
if (data.isEmpty()) {
|
||||
if (f.isOpen()) f.close();
|
||||
if (!access) {
|
||||
if (!file.accessEnable()) {
|
||||
LOG(("Audio Error: could not open file access '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(f.error()).arg(f.errorString()));
|
||||
return false;
|
||||
}
|
||||
access = true;
|
||||
}
|
||||
f.setFileName(file.name());
|
||||
if (!f.open(QIODevice::ReadOnly)) {
|
||||
LOG(("Audio Error: could not open file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(f.error()).arg(f.errorString()));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
dataPos = 0;
|
||||
return true;
|
||||
}
|
63
Telegram/SourceFiles/media/media_audio_loader.h
Normal file
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
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
|
||||
|
||||
class AudioPlayerLoader {
|
||||
public:
|
||||
AudioPlayerLoader(const FileLocation &file, const QByteArray &data);
|
||||
virtual ~AudioPlayerLoader();
|
||||
|
||||
virtual bool check(const FileLocation &file, const QByteArray &data);
|
||||
|
||||
virtual bool open(qint64 &position) = 0;
|
||||
virtual int64 duration() = 0;
|
||||
virtual int32 frequency() = 0;
|
||||
virtual int32 format() = 0;
|
||||
|
||||
enum class ReadResult {
|
||||
Error,
|
||||
NotYet,
|
||||
Ok,
|
||||
Wait,
|
||||
EndOfFile,
|
||||
};
|
||||
virtual ReadResult readMore(QByteArray &samples, int64 &samplesCount) = 0;
|
||||
|
||||
void saveDecodedSamples(QByteArray *samples, int64 *samplesCount);
|
||||
void takeSavedDecodedSamples(QByteArray *samples, int64 *samplesCount);
|
||||
bool holdsSavedDecodedSamples() const;
|
||||
|
||||
protected:
|
||||
FileLocation file;
|
||||
bool access = false;
|
||||
QByteArray data;
|
||||
|
||||
QFile f;
|
||||
int32 dataPos = 0;
|
||||
|
||||
bool openFile();
|
||||
|
||||
private:
|
||||
QByteArray _savedSamples;
|
||||
int64 _savedSamplesCount = 0;
|
||||
bool _holdsSavedSamples = false;
|
||||
|
||||
};
|
435
Telegram/SourceFiles/media/media_audio_loaders.cpp
Normal file
|
@ -0,0 +1,435 @@
|
|||
/*
|
||||
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 "media/media_audio_loaders.h"
|
||||
|
||||
#include "media/media_audio.h"
|
||||
#include "media/media_audio_ffmpeg_loader.h"
|
||||
#include "media/media_child_ffmpeg_loader.h"
|
||||
|
||||
AudioPlayerLoaders::AudioPlayerLoaders(QThread *thread) : _fromVideoNotify(this, "onVideoSoundAdded") {
|
||||
moveToThread(thread);
|
||||
}
|
||||
|
||||
void AudioPlayerLoaders::feedFromVideo(VideoSoundPart &&part) {
|
||||
bool invoke = false;
|
||||
{
|
||||
QMutexLocker lock(&_fromVideoMutex);
|
||||
if (_fromVideoPlayId == part.videoPlayId) {
|
||||
_fromVideoQueue.enqueue(FFMpeg::dataWrapFromPacket(*part.packet));
|
||||
invoke = true;
|
||||
} else {
|
||||
FFMpeg::freePacket(part.packet);
|
||||
}
|
||||
}
|
||||
if (invoke) {
|
||||
_fromVideoNotify.call();
|
||||
}
|
||||
}
|
||||
|
||||
void AudioPlayerLoaders::startFromVideo(uint64 videoPlayId) {
|
||||
QMutexLocker lock(&_fromVideoMutex);
|
||||
_fromVideoPlayId = videoPlayId;
|
||||
clearFromVideoQueue();
|
||||
}
|
||||
|
||||
void AudioPlayerLoaders::stopFromVideo() {
|
||||
startFromVideo(0);
|
||||
}
|
||||
|
||||
void AudioPlayerLoaders::onVideoSoundAdded() {
|
||||
bool waitingAndAdded = false;
|
||||
{
|
||||
QMutexLocker lock(&_fromVideoMutex);
|
||||
if (_videoLoader && _videoLoader->playId() == _fromVideoPlayId && !_fromVideoQueue.isEmpty()) {
|
||||
_videoLoader->enqueuePackets(_fromVideoQueue);
|
||||
waitingAndAdded = _videoLoader->holdsSavedDecodedSamples();
|
||||
}
|
||||
}
|
||||
if (waitingAndAdded) {
|
||||
onLoad(_video);
|
||||
}
|
||||
}
|
||||
|
||||
AudioPlayerLoaders::~AudioPlayerLoaders() {
|
||||
QMutexLocker lock(&_fromVideoMutex);
|
||||
clearFromVideoQueue();
|
||||
}
|
||||
|
||||
void AudioPlayerLoaders::clearFromVideoQueue() {
|
||||
auto queue = createAndSwap(_fromVideoQueue);
|
||||
for (auto &packetData : queue) {
|
||||
AVPacket packet;
|
||||
FFMpeg::packetFromDataWrap(packet, packetData);
|
||||
FFMpeg::freePacket(&packet);
|
||||
}
|
||||
}
|
||||
|
||||
void AudioPlayerLoaders::onInit() {
|
||||
}
|
||||
|
||||
void AudioPlayerLoaders::onStart(const AudioMsgId &audio, qint64 position) {
|
||||
auto type = audio.type();
|
||||
clear(type);
|
||||
{
|
||||
QMutexLocker lock(internal::audioPlayerMutex());
|
||||
AudioPlayer *voice = audioPlayer();
|
||||
if (!voice) return;
|
||||
|
||||
auto data = voice->dataForType(type);
|
||||
if (!data) return;
|
||||
|
||||
data->loading = true;
|
||||
}
|
||||
|
||||
loadData(audio, position);
|
||||
}
|
||||
|
||||
AudioMsgId AudioPlayerLoaders::clear(AudioMsgId::Type type) {
|
||||
AudioMsgId result;
|
||||
switch (type) {
|
||||
case AudioMsgId::Type::Voice: std::swap(result, _audio); _audioLoader = nullptr; break;
|
||||
case AudioMsgId::Type::Song: std::swap(result, _song); _songLoader = nullptr; break;
|
||||
case AudioMsgId::Type::Video: std::swap(result, _video); _videoLoader = nullptr; break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void AudioPlayerLoaders::setStoppedState(AudioPlayer::AudioMsg *m, AudioPlayerState state) {
|
||||
m->playbackState.state = state;
|
||||
m->playbackState.position = 0;
|
||||
}
|
||||
|
||||
void AudioPlayerLoaders::emitError(AudioMsgId::Type type) {
|
||||
emit error(clear(type));
|
||||
}
|
||||
|
||||
void AudioPlayerLoaders::onLoad(const AudioMsgId &audio) {
|
||||
loadData(audio, 0);
|
||||
}
|
||||
|
||||
void AudioPlayerLoaders::loadData(AudioMsgId audio, qint64 position) {
|
||||
SetupError err = SetupNoErrorStarted;
|
||||
auto type = audio.type();
|
||||
AudioPlayerLoader *l = setupLoader(audio, err, position);
|
||||
if (!l) {
|
||||
if (err == SetupErrorAtStart) {
|
||||
emitError(type);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
bool started = (err == SetupNoErrorStarted);
|
||||
bool finished = false;
|
||||
bool waiting = false;
|
||||
bool errAtStart = started;
|
||||
|
||||
QByteArray samples;
|
||||
int64 samplesCount = 0;
|
||||
if (l->holdsSavedDecodedSamples()) {
|
||||
l->takeSavedDecodedSamples(&samples, &samplesCount);
|
||||
}
|
||||
while (samples.size() < AudioVoiceMsgBufferSize) {
|
||||
auto res = l->readMore(samples, samplesCount);
|
||||
using Result = AudioPlayerLoader::ReadResult;
|
||||
if (res == Result::Error) {
|
||||
if (errAtStart) {
|
||||
{
|
||||
QMutexLocker lock(internal::audioPlayerMutex());
|
||||
AudioPlayer::AudioMsg *m = checkLoader(type);
|
||||
if (m) m->playbackState.state = AudioPlayerStoppedAtStart;
|
||||
}
|
||||
emitError(type);
|
||||
return;
|
||||
}
|
||||
finished = true;
|
||||
break;
|
||||
} else if (res == Result::EndOfFile) {
|
||||
finished = true;
|
||||
break;
|
||||
} else if (res == Result::Ok) {
|
||||
errAtStart = false;
|
||||
} else if (res == Result::Wait) {
|
||||
waiting = (samples.size() < AudioVoiceMsgBufferSize);
|
||||
if (waiting) {
|
||||
l->saveDecodedSamples(&samples, &samplesCount);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
QMutexLocker lock(internal::audioPlayerMutex());
|
||||
if (!checkLoader(type)) {
|
||||
clear(type);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
QMutexLocker lock(internal::audioPlayerMutex());
|
||||
AudioPlayer::AudioMsg *m = checkLoader(type);
|
||||
if (!m) {
|
||||
clear(type);
|
||||
return;
|
||||
}
|
||||
|
||||
if (started) {
|
||||
if (m->source) {
|
||||
alSourceStop(m->source);
|
||||
for (int32 i = 0; i < 3; ++i) {
|
||||
if (m->samplesCount[i]) {
|
||||
ALuint buffer = 0;
|
||||
alSourceUnqueueBuffers(m->source, 1, &buffer);
|
||||
m->samplesCount[i] = 0;
|
||||
}
|
||||
}
|
||||
m->nextBuffer = 0;
|
||||
}
|
||||
m->skipStart = position;
|
||||
m->skipEnd = m->playbackState.duration - position;
|
||||
m->playbackState.position = position;
|
||||
m->started = 0;
|
||||
}
|
||||
if (samplesCount) {
|
||||
if (!m->source) {
|
||||
alGenSources(1, &m->source);
|
||||
alSourcef(m->source, AL_PITCH, 1.f);
|
||||
alSource3f(m->source, AL_POSITION, 0, 0, 0);
|
||||
alSource3f(m->source, AL_VELOCITY, 0, 0, 0);
|
||||
alSourcei(m->source, AL_LOOPING, 0);
|
||||
}
|
||||
if (!m->buffers[m->nextBuffer]) {
|
||||
alGenBuffers(3, m->buffers);
|
||||
}
|
||||
|
||||
// If this buffer is queued, try to unqueue some buffer.
|
||||
if (m->samplesCount[m->nextBuffer]) {
|
||||
ALint processed = 0;
|
||||
alGetSourcei(m->source, AL_BUFFERS_PROCESSED, &processed);
|
||||
if (processed < 1) { // No processed buffers, wait.
|
||||
l->saveDecodedSamples(&samples, &samplesCount);
|
||||
return;
|
||||
}
|
||||
|
||||
// Unqueue some processed buffer.
|
||||
ALuint buffer = 0;
|
||||
alSourceUnqueueBuffers(m->source, 1, &buffer);
|
||||
if (!internal::audioCheckError()) {
|
||||
setStoppedState(m, AudioPlayerStoppedAtError);
|
||||
emitError(type);
|
||||
return;
|
||||
}
|
||||
|
||||
// Find it in the list and make it the nextBuffer.
|
||||
bool found = false;
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
if (m->buffers[i] == buffer) {
|
||||
found = true;
|
||||
m->nextBuffer = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
LOG(("Audio Error: Could not find the unqueued buffer! Buffer %1 in source %2 with processed count %3").arg(buffer).arg(m->source).arg(processed));
|
||||
setStoppedState(m, AudioPlayerStoppedAtError);
|
||||
emitError(type);
|
||||
return;
|
||||
}
|
||||
|
||||
if (m->samplesCount[m->nextBuffer]) {
|
||||
m->skipStart += m->samplesCount[m->nextBuffer];
|
||||
m->samplesCount[m->nextBuffer] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
auto frequency = l->frequency();
|
||||
auto format = l->format();
|
||||
m->samplesCount[m->nextBuffer] = samplesCount;
|
||||
alBufferData(m->buffers[m->nextBuffer], format, samples.constData(), samples.size(), frequency);
|
||||
|
||||
alSourceQueueBuffers(m->source, 1, m->buffers + m->nextBuffer);
|
||||
m->skipEnd -= samplesCount;
|
||||
|
||||
m->nextBuffer = (m->nextBuffer + 1) % 3;
|
||||
|
||||
if (!internal::audioCheckError()) {
|
||||
setStoppedState(m, AudioPlayerStoppedAtError);
|
||||
emitError(type);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (waiting) {
|
||||
return;
|
||||
}
|
||||
finished = true;
|
||||
}
|
||||
|
||||
if (finished) {
|
||||
m->skipEnd = 0;
|
||||
m->playbackState.duration = m->skipStart + m->samplesCount[0] + m->samplesCount[1] + m->samplesCount[2];
|
||||
clear(type);
|
||||
}
|
||||
|
||||
m->loading = false;
|
||||
if (m->playbackState.state == AudioPlayerResuming || m->playbackState.state == AudioPlayerPlaying || m->playbackState.state == AudioPlayerStarting) {
|
||||
ALint state = AL_INITIAL;
|
||||
alGetSourcei(m->source, AL_SOURCE_STATE, &state);
|
||||
if (internal::audioCheckError()) {
|
||||
if (state != AL_PLAYING) {
|
||||
audioPlayer()->resumeDevice();
|
||||
|
||||
switch (type) {
|
||||
case AudioMsgId::Type::Voice: alSourcef(m->source, AL_GAIN, internal::audioSuppressGain()); break;
|
||||
case AudioMsgId::Type::Song: alSourcef(m->source, AL_GAIN, internal::audioSuppressSongGain() * Global::SongVolume()); break;
|
||||
case AudioMsgId::Type::Video: alSourcef(m->source, AL_GAIN, internal::audioSuppressSongGain() * Global::VideoVolume()); break;
|
||||
}
|
||||
if (!internal::audioCheckError()) {
|
||||
setStoppedState(m, AudioPlayerStoppedAtError);
|
||||
emitError(type);
|
||||
return;
|
||||
}
|
||||
|
||||
alSourcePlay(m->source);
|
||||
if (!internal::audioCheckError()) {
|
||||
setStoppedState(m, AudioPlayerStoppedAtError);
|
||||
emitError(type);
|
||||
return;
|
||||
}
|
||||
|
||||
emit needToCheck();
|
||||
}
|
||||
} else {
|
||||
setStoppedState(m, AudioPlayerStoppedAtError);
|
||||
emitError(type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AudioPlayerLoader *AudioPlayerLoaders::setupLoader(const AudioMsgId &audio, SetupError &err, qint64 &position) {
|
||||
err = SetupErrorAtStart;
|
||||
QMutexLocker lock(internal::audioPlayerMutex());
|
||||
AudioPlayer *voice = audioPlayer();
|
||||
if (!voice) return nullptr;
|
||||
|
||||
auto data = voice->dataForType(audio.type());
|
||||
if (!data || data->audio != audio || !data->loading) {
|
||||
emit error(audio);
|
||||
LOG(("Audio Error: trying to load part of audio, that is not current at the moment"));
|
||||
err = SetupErrorNotPlaying;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool isGoodId = false;
|
||||
AudioPlayerLoader *l = nullptr;
|
||||
switch (audio.type()) {
|
||||
case AudioMsgId::Type::Voice: l = _audioLoader.get(); isGoodId = (_audio == audio); break;
|
||||
case AudioMsgId::Type::Song: l = _songLoader.get(); isGoodId = (_song == audio); break;
|
||||
case AudioMsgId::Type::Video: l = _videoLoader.get(); isGoodId = (_video == audio); break;
|
||||
}
|
||||
|
||||
if (l && (!isGoodId || !l->check(data->file, data->data))) {
|
||||
clear(audio.type());
|
||||
l = nullptr;
|
||||
}
|
||||
|
||||
if (!l) {
|
||||
std_::unique_ptr<AudioPlayerLoader> *loader = nullptr;
|
||||
switch (audio.type()) {
|
||||
case AudioMsgId::Type::Voice: _audio = audio; loader = &_audioLoader; break;
|
||||
case AudioMsgId::Type::Song: _song = audio; loader = &_songLoader; break;
|
||||
case AudioMsgId::Type::Video: _video = audio; break;
|
||||
}
|
||||
|
||||
if (audio.type() == AudioMsgId::Type::Video) {
|
||||
if (!data->videoData) {
|
||||
data->playbackState.state = AudioPlayerStoppedAtError;
|
||||
emit error(audio);
|
||||
LOG(("Audio Error: video sound data not ready"));
|
||||
return nullptr;
|
||||
}
|
||||
_videoLoader = std_::make_unique<ChildFFMpegLoader>(data->videoPlayId, std_::move(data->videoData));
|
||||
l = _videoLoader.get();
|
||||
} else {
|
||||
*loader = std_::make_unique<FFMpegLoader>(data->file, data->data);
|
||||
l = loader->get();
|
||||
}
|
||||
|
||||
if (!l->open(position)) {
|
||||
data->playbackState.state = AudioPlayerStoppedAtStart;
|
||||
return nullptr;
|
||||
}
|
||||
int64 duration = l->duration();
|
||||
if (duration <= 0) {
|
||||
data->playbackState.state = AudioPlayerStoppedAtStart;
|
||||
return nullptr;
|
||||
}
|
||||
data->playbackState.duration = duration;
|
||||
data->playbackState.frequency = l->frequency();
|
||||
if (!data->playbackState.frequency) data->playbackState.frequency = AudioVoiceMsgFrequency;
|
||||
err = SetupNoErrorStarted;
|
||||
} else {
|
||||
if (!data->skipEnd) {
|
||||
err = SetupErrorLoadedFull;
|
||||
LOG(("Audio Error: trying to load part of audio, that is already loaded to the end"));
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
return l;
|
||||
}
|
||||
|
||||
AudioPlayer::AudioMsg *AudioPlayerLoaders::checkLoader(AudioMsgId::Type type) {
|
||||
AudioPlayer *voice = audioPlayer();
|
||||
if (!voice) return 0;
|
||||
|
||||
auto data = voice->dataForType(type);
|
||||
bool isGoodId = false;
|
||||
AudioPlayerLoader *l = nullptr;
|
||||
switch (type) {
|
||||
case AudioMsgId::Type::Voice: l = _audioLoader.get(); isGoodId = (data->audio == _audio); break;
|
||||
case AudioMsgId::Type::Song: l = _songLoader.get(); isGoodId = (data->audio == _song); break;
|
||||
case AudioMsgId::Type::Video: l = _videoLoader.get(); isGoodId = (data->audio == _video); break;
|
||||
}
|
||||
if (!l || !data) return nullptr;
|
||||
|
||||
if (!isGoodId || !data->loading || !l->check(data->file, data->data)) {
|
||||
LOG(("Audio Error: playing changed while loading"));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
void AudioPlayerLoaders::onCancel(const AudioMsgId &audio) {
|
||||
switch (audio.type()) {
|
||||
case AudioMsgId::Type::Voice: if (_audio == audio) clear(audio.type()); break;
|
||||
case AudioMsgId::Type::Song: if (_song == audio) clear(audio.type()); break;
|
||||
case AudioMsgId::Type::Video: if (_video == audio) clear(audio.type()); break;
|
||||
}
|
||||
|
||||
QMutexLocker lock(internal::audioPlayerMutex());
|
||||
AudioPlayer *voice = audioPlayer();
|
||||
if (!voice) return;
|
||||
|
||||
for (int i = 0; i < AudioSimultaneousLimit; ++i) {
|
||||
auto data = voice->dataForType(audio.type(), i);
|
||||
if (data->audio == audio) {
|
||||
data->loading = false;
|
||||
}
|
||||
}
|
||||
}
|
79
Telegram/SourceFiles/media/media_audio_loaders.h
Normal file
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
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 "media/media_child_ffmpeg_loader.h"
|
||||
#include "media/media_audio.h"
|
||||
#include "media/media_child_ffmpeg_loader.h"
|
||||
|
||||
class AudioPlayerLoader;
|
||||
class ChildFFMpegLoader;
|
||||
class AudioPlayerLoaders : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
AudioPlayerLoaders(QThread *thread);
|
||||
void startFromVideo(uint64 videoPlayId);
|
||||
void stopFromVideo();
|
||||
void feedFromVideo(VideoSoundPart &&part);
|
||||
~AudioPlayerLoaders();
|
||||
|
||||
signals:
|
||||
void error(const AudioMsgId &audio);
|
||||
void needToCheck();
|
||||
|
||||
public slots:
|
||||
void onInit();
|
||||
|
||||
void onStart(const AudioMsgId &audio, qint64 position);
|
||||
void onLoad(const AudioMsgId &audio);
|
||||
void onCancel(const AudioMsgId &audio);
|
||||
|
||||
void onVideoSoundAdded();
|
||||
|
||||
private:
|
||||
void clearFromVideoQueue();
|
||||
|
||||
AudioMsgId _audio, _song, _video;
|
||||
std_::unique_ptr<AudioPlayerLoader> _audioLoader;
|
||||
std_::unique_ptr<AudioPlayerLoader> _songLoader;
|
||||
std_::unique_ptr<ChildFFMpegLoader> _videoLoader;
|
||||
|
||||
QMutex _fromVideoMutex;
|
||||
uint64 _fromVideoPlayId;
|
||||
QQueue<FFMpeg::AVPacketDataWrap> _fromVideoQueue;
|
||||
SingleDelayedCall _fromVideoNotify;
|
||||
|
||||
void emitError(AudioMsgId::Type type);
|
||||
AudioMsgId clear(AudioMsgId::Type type);
|
||||
void setStoppedState(AudioPlayer::AudioMsg *m, AudioPlayerState state = AudioPlayerStopped);
|
||||
|
||||
enum SetupError {
|
||||
SetupErrorAtStart = 0,
|
||||
SetupErrorNotPlaying = 1,
|
||||
SetupErrorLoadedFull = 2,
|
||||
SetupNoErrorStarted = 3,
|
||||
};
|
||||
void loadData(AudioMsgId audio, qint64 position);
|
||||
AudioPlayerLoader *setupLoader(const AudioMsgId &audio, SetupError &err, qint64 &position);
|
||||
AudioPlayer::AudioMsg *checkLoader(AudioMsgId::Type type);
|
||||
|
||||
};
|
212
Telegram/SourceFiles/media/media_child_ffmpeg_loader.cpp
Normal file
|
@ -0,0 +1,212 @@
|
|||
/*
|
||||
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 "media/media_child_ffmpeg_loader.h"
|
||||
|
||||
constexpr AVSampleFormat AudioToFormat = AV_SAMPLE_FMT_S16;
|
||||
constexpr int64_t AudioToChannelLayout = AV_CH_LAYOUT_STEREO;
|
||||
constexpr int32 AudioToChannels = 2;
|
||||
|
||||
VideoSoundData::~VideoSoundData() {
|
||||
if (context) {
|
||||
avcodec_close(context);
|
||||
avcodec_free_context(&context);
|
||||
context = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
ChildFFMpegLoader::ChildFFMpegLoader(uint64 videoPlayId, std_::unique_ptr<VideoSoundData> &&data) : AudioPlayerLoader(FileLocation(), QByteArray())
|
||||
, _videoPlayId(videoPlayId)
|
||||
, _parentData(std_::move(data)) {
|
||||
_frame = av_frame_alloc();
|
||||
}
|
||||
|
||||
bool ChildFFMpegLoader::open(qint64 &position) {
|
||||
int res = 0;
|
||||
char err[AV_ERROR_MAX_STRING_SIZE] = { 0 };
|
||||
|
||||
uint64_t layout = _parentData->context->channel_layout;
|
||||
_inputFormat = _parentData->context->sample_fmt;
|
||||
switch (layout) {
|
||||
case AV_CH_LAYOUT_MONO:
|
||||
switch (_inputFormat) {
|
||||
case AV_SAMPLE_FMT_U8:
|
||||
case AV_SAMPLE_FMT_U8P: _format = AL_FORMAT_MONO8; _sampleSize = 1; break;
|
||||
case AV_SAMPLE_FMT_S16:
|
||||
case AV_SAMPLE_FMT_S16P: _format = AL_FORMAT_MONO16; _sampleSize = sizeof(uint16); break;
|
||||
default:
|
||||
_sampleSize = -1; // convert needed
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case AV_CH_LAYOUT_STEREO:
|
||||
switch (_inputFormat) {
|
||||
case AV_SAMPLE_FMT_U8: _format = AL_FORMAT_STEREO8; _sampleSize = 2; break;
|
||||
case AV_SAMPLE_FMT_S16: _format = AL_FORMAT_STEREO16; _sampleSize = 2 * sizeof(uint16); break;
|
||||
default:
|
||||
_sampleSize = -1; // convert needed
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
_sampleSize = -1; // convert needed
|
||||
break;
|
||||
}
|
||||
if (_parentData->frequency != 44100 && _parentData->frequency != 48000) {
|
||||
_sampleSize = -1; // convert needed
|
||||
}
|
||||
|
||||
if (_sampleSize < 0) {
|
||||
_swrContext = swr_alloc();
|
||||
if (!_swrContext) {
|
||||
LOG(("Audio Error: Unable to swr_alloc for file '%1', data size '%2'").arg(file.name()).arg(data.size()));
|
||||
return false;
|
||||
}
|
||||
int64_t src_ch_layout = layout, dst_ch_layout = AudioToChannelLayout;
|
||||
_srcRate = _parentData->frequency;
|
||||
AVSampleFormat src_sample_fmt = _inputFormat, dst_sample_fmt = AudioToFormat;
|
||||
_dstRate = (_parentData->frequency != 44100 && _parentData->frequency != 48000) ? AudioVoiceMsgFrequency : _parentData->frequency;
|
||||
|
||||
av_opt_set_int(_swrContext, "in_channel_layout", src_ch_layout, 0);
|
||||
av_opt_set_int(_swrContext, "in_sample_rate", _srcRate, 0);
|
||||
av_opt_set_sample_fmt(_swrContext, "in_sample_fmt", src_sample_fmt, 0);
|
||||
av_opt_set_int(_swrContext, "out_channel_layout", dst_ch_layout, 0);
|
||||
av_opt_set_int(_swrContext, "out_sample_rate", _dstRate, 0);
|
||||
av_opt_set_sample_fmt(_swrContext, "out_sample_fmt", dst_sample_fmt, 0);
|
||||
|
||||
if ((res = swr_init(_swrContext)) < 0) {
|
||||
LOG(("Audio Error: Unable to swr_init for file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res)));
|
||||
return false;
|
||||
}
|
||||
|
||||
_sampleSize = AudioToChannels * sizeof(short);
|
||||
_parentData->frequency = _dstRate;
|
||||
_parentData->length = av_rescale_rnd(_parentData->length, _dstRate, _srcRate, AV_ROUND_UP);
|
||||
position = av_rescale_rnd(position, _dstRate, _srcRate, AV_ROUND_DOWN);
|
||||
_format = AL_FORMAT_STEREO16;
|
||||
|
||||
_maxResampleSamples = av_rescale_rnd(AVBlockSize / _sampleSize, _dstRate, _srcRate, AV_ROUND_UP);
|
||||
if ((res = av_samples_alloc_array_and_samples(&_dstSamplesData, 0, AudioToChannels, _maxResampleSamples, AudioToFormat, 0)) < 0) {
|
||||
LOG(("Audio Error: Unable to av_samples_alloc for file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res)));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
AudioPlayerLoader::ReadResult ChildFFMpegLoader::readMore(QByteArray &result, int64 &samplesAdded) {
|
||||
int res;
|
||||
|
||||
av_frame_unref(_frame);
|
||||
res = avcodec_receive_frame(_parentData->context, _frame);
|
||||
if (res >= 0) {
|
||||
return readFromReadyFrame(result, samplesAdded);
|
||||
}
|
||||
|
||||
if (res == AVERROR_EOF) {
|
||||
return ReadResult::EndOfFile;
|
||||
} else if (res != AVERROR(EAGAIN)) {
|
||||
char err[AV_ERROR_MAX_STRING_SIZE] = { 0 };
|
||||
LOG(("Audio Error: Unable to avcodec_receive_frame() file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res)));
|
||||
return ReadResult::Error;
|
||||
}
|
||||
|
||||
if (_queue.isEmpty()) {
|
||||
return _eofReached ? ReadResult::EndOfFile : ReadResult::Wait;
|
||||
}
|
||||
|
||||
AVPacket packet;
|
||||
FFMpeg::packetFromDataWrap(packet, _queue.dequeue());
|
||||
|
||||
_eofReached = FFMpeg::isNullPacket(packet);
|
||||
if (_eofReached) {
|
||||
avcodec_send_packet(_parentData->context, nullptr); // drain
|
||||
return ReadResult::Ok;
|
||||
}
|
||||
|
||||
res = avcodec_send_packet(_parentData->context, &packet);
|
||||
if (res < 0) {
|
||||
FFMpeg::freePacket(&packet);
|
||||
|
||||
char err[AV_ERROR_MAX_STRING_SIZE] = { 0 };
|
||||
LOG(("Audio Error: Unable to avcodec_send_packet() file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res)));
|
||||
if (res == AVERROR_INVALIDDATA) {
|
||||
return ReadResult::NotYet; // try to skip bad packet
|
||||
}
|
||||
return ReadResult::Error;
|
||||
}
|
||||
FFMpeg::freePacket(&packet);
|
||||
return ReadResult::Ok;
|
||||
}
|
||||
|
||||
AudioPlayerLoader::ReadResult ChildFFMpegLoader::readFromReadyFrame(QByteArray &result, int64 &samplesAdded) {
|
||||
int res = 0;
|
||||
|
||||
if (_dstSamplesData) { // convert needed
|
||||
int64_t dstSamples = av_rescale_rnd(swr_get_delay(_swrContext, _srcRate) + _frame->nb_samples, _dstRate, _srcRate, AV_ROUND_UP);
|
||||
if (dstSamples > _maxResampleSamples) {
|
||||
_maxResampleSamples = dstSamples;
|
||||
av_free(_dstSamplesData[0]);
|
||||
|
||||
if ((res = av_samples_alloc(_dstSamplesData, 0, AudioToChannels, _maxResampleSamples, AudioToFormat, 1)) < 0) {
|
||||
_dstSamplesData[0] = 0;
|
||||
char err[AV_ERROR_MAX_STRING_SIZE] = { 0 };
|
||||
LOG(("Audio Error: Unable to av_samples_alloc for file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res)));
|
||||
return ReadResult::Error;
|
||||
}
|
||||
}
|
||||
if ((res = swr_convert(_swrContext, _dstSamplesData, dstSamples, (const uint8_t**)_frame->extended_data, _frame->nb_samples)) < 0) {
|
||||
char err[AV_ERROR_MAX_STRING_SIZE] = { 0 };
|
||||
LOG(("Audio Error: Unable to swr_convert for file '%1', data size '%2', error %3, %4").arg(file.name()).arg(data.size()).arg(res).arg(av_make_error_string(err, sizeof(err), res)));
|
||||
return ReadResult::Error;
|
||||
}
|
||||
int32 resultLen = av_samples_get_buffer_size(0, AudioToChannels, res, AudioToFormat, 1);
|
||||
result.append((const char*)_dstSamplesData[0], resultLen);
|
||||
samplesAdded += resultLen / _sampleSize;
|
||||
} else {
|
||||
result.append((const char*)_frame->extended_data[0], _frame->nb_samples * _sampleSize);
|
||||
samplesAdded += _frame->nb_samples;
|
||||
}
|
||||
return ReadResult::Ok;
|
||||
}
|
||||
|
||||
void ChildFFMpegLoader::enqueuePackets(QQueue<FFMpeg::AVPacketDataWrap> &packets) {
|
||||
_queue += std_::move(packets);
|
||||
packets.clear();
|
||||
}
|
||||
|
||||
ChildFFMpegLoader::~ChildFFMpegLoader() {
|
||||
auto queue = createAndSwap(_queue);
|
||||
for (auto &packetData : queue) {
|
||||
AVPacket packet;
|
||||
FFMpeg::packetFromDataWrap(packet, packetData);
|
||||
FFMpeg::freePacket(&packet);
|
||||
}
|
||||
if (_swrContext) swr_free(&_swrContext);
|
||||
if (_dstSamplesData) {
|
||||
if (_dstSamplesData[0]) {
|
||||
av_freep(&_dstSamplesData[0]);
|
||||
}
|
||||
av_freep(&_dstSamplesData);
|
||||
}
|
||||
av_frame_free(&_frame);
|
||||
}
|
136
Telegram/SourceFiles/media/media_child_ffmpeg_loader.h
Normal file
|
@ -0,0 +1,136 @@
|
|||
/*
|
||||
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 "media/media_audio_loader.h"
|
||||
|
||||
extern "C" {
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libavutil/opt.h>
|
||||
#include <libswresample/swresample.h>
|
||||
} // extern "C"
|
||||
|
||||
#include <AL/al.h>
|
||||
|
||||
struct VideoSoundData {
|
||||
AVCodecContext *context = nullptr;
|
||||
int32 frequency = AudioVoiceMsgFrequency;
|
||||
int64 length = 0;
|
||||
~VideoSoundData();
|
||||
};
|
||||
|
||||
struct VideoSoundPart {
|
||||
AVPacket *packet = nullptr;
|
||||
uint64 videoPlayId = 0;
|
||||
};
|
||||
|
||||
namespace FFMpeg {
|
||||
|
||||
// AVPacket has a deprecated field, so when you copy an AVPacket
|
||||
// variable (e.g. inside QQueue), a compile warning is emited.
|
||||
// We wrap full AVPacket data in a new AVPacketDataWrap struct.
|
||||
// All other fields are copied from AVPacket without modifications.
|
||||
struct AVPacketDataWrap {
|
||||
char __data[sizeof(AVPacket)];
|
||||
};
|
||||
|
||||
inline void packetFromDataWrap(AVPacket &packet, const AVPacketDataWrap &data) {
|
||||
memcpy(&packet, &data, sizeof(data));
|
||||
}
|
||||
|
||||
inline AVPacketDataWrap dataWrapFromPacket(const AVPacket &packet) {
|
||||
AVPacketDataWrap data;
|
||||
memcpy(&data, &packet, sizeof(data));
|
||||
return data;
|
||||
}
|
||||
|
||||
inline bool isNullPacket(const AVPacket &packet) {
|
||||
return packet.data == nullptr && packet.size == 0;
|
||||
}
|
||||
|
||||
inline bool isNullPacket(const AVPacket *packet) {
|
||||
return isNullPacket(*packet);
|
||||
}
|
||||
|
||||
inline void freePacket(AVPacket *packet) {
|
||||
if (!isNullPacket(packet)) {
|
||||
av_packet_unref(packet);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace FFMpeg
|
||||
|
||||
class ChildFFMpegLoader : public AudioPlayerLoader {
|
||||
public:
|
||||
ChildFFMpegLoader(uint64 videoPlayId, std_::unique_ptr<VideoSoundData> &&data);
|
||||
|
||||
bool open(qint64 &position) override;
|
||||
|
||||
bool check(const FileLocation &file, const QByteArray &data) override {
|
||||
return true;
|
||||
}
|
||||
|
||||
int32 format() override {
|
||||
return _format;
|
||||
}
|
||||
|
||||
int64 duration() override {
|
||||
return _parentData->length;
|
||||
}
|
||||
|
||||
int32 frequency() override {
|
||||
return _parentData->frequency;
|
||||
}
|
||||
|
||||
ReadResult readMore(QByteArray &result, int64 &samplesAdded) override;
|
||||
void enqueuePackets(QQueue<FFMpeg::AVPacketDataWrap> &packets);
|
||||
|
||||
uint64 playId() const {
|
||||
return _videoPlayId;
|
||||
}
|
||||
bool eofReached() const {
|
||||
return _eofReached;
|
||||
}
|
||||
|
||||
~ChildFFMpegLoader();
|
||||
|
||||
private:
|
||||
ReadResult readFromReadyFrame(QByteArray &result, int64 &samplesAdded);
|
||||
|
||||
bool _eofReached = false;
|
||||
|
||||
int32 _sampleSize = 2 * sizeof(uint16);
|
||||
int32 _format = AL_FORMAT_STEREO16;
|
||||
int32 _srcRate = AudioVoiceMsgFrequency;
|
||||
int32 _dstRate = AudioVoiceMsgFrequency;
|
||||
int32 _maxResampleSamples = 1024;
|
||||
uint8_t **_dstSamplesData = nullptr;
|
||||
|
||||
uint64 _videoPlayId = 0;
|
||||
std_::unique_ptr<VideoSoundData> _parentData;
|
||||
AVSampleFormat _inputFormat;
|
||||
AVFrame *_frame = nullptr;
|
||||
|
||||
SwrContext *_swrContext = nullptr;
|
||||
QQueue<FFMpeg::AVPacketDataWrap> _queue;
|
||||
|
||||
};
|