Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
94cf307ae0 | ||
|
|
2cc1fde5e4 | ||
|
|
6796ac688a | ||
|
|
9551cfaf9b | ||
|
|
8ef9ec0567 | ||
|
|
af552fb4c0 | ||
|
|
ae7e5be5cd | ||
|
|
74b126f309 | ||
|
|
26e023058c | ||
|
|
6236590ca4 | ||
|
|
ea51f976f2 | ||
|
|
719f3428ec | ||
|
|
2df4d19474 | ||
|
|
2a409e3734 | ||
|
|
0171a4e874 | ||
|
|
59e5ffe743 | ||
|
|
2bcbb5a5be | ||
|
|
5b4694a4eb | ||
|
|
54d6673d0b | ||
|
|
e07a7a4b4c | ||
|
|
f2d11e7432 | ||
|
|
1a115cc7e5 | ||
|
|
a00941f7ce | ||
|
|
634d21e486 | ||
|
|
95d8742e3c | ||
|
|
bd8dee0972 |
@@ -137,8 +137,9 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
"lng_error_cant_add_admin_unban" = "Sorry, you can't add this user as an admin because they are in the blacklist and you can't unban them.";
|
||||
"lng_error_cant_ban_admin" = "Sorry, you can't ban this user because they are an admin in this group and you are not allowed to demote them.";
|
||||
"lng_sure_add_admin_invite" = "This user is not a member of this group. Add them to the group and promote them to admin?";
|
||||
"lng_sure_add_admin_unban" = "This user is currently restricted or banned in this group. Are you sure you want to unban and promote them?";
|
||||
"lng_sure_ban_admin" = "This user is an admin in this group. Are you sure you want to go ahead and restrict them?";
|
||||
"lng_sure_add_admin_invite_channel" = "This user is not a subscriber of this channel. Add them to the channel and promote them to admin?";
|
||||
"lng_sure_add_admin_unban" = "This user is currently restricted or banned. Are you sure you want to unban and promote them?";
|
||||
"lng_sure_ban_admin" = "This user is an admin. Are you sure you want to go ahead and restrict them?";
|
||||
"lng_sure_ban_user_group" = "Ban {user} in the group?";
|
||||
"lng_sure_enable_socks" = "Are you sure you want to enable this proxy?\n\nServer: {server}\nPort: {port}\n\nYou can change your proxy server later in the Settings (Connection Type).";
|
||||
"lng_sure_enable" = "Enable";
|
||||
@@ -577,8 +578,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
"lng_profile_kick" = "Remove";
|
||||
"lng_profile_sure_kick" = "Remove {user} from the group?";
|
||||
"lng_profile_sure_kick_channel" = "Remove {user} from the channel?";
|
||||
"lng_profile_sure_remove_admin" = "Remove {user} from group admins?";
|
||||
"lng_profile_sure_remove_admin_channel" = "Remove {user} from channel admins?";
|
||||
"lng_profile_sure_remove_admin" = "Remove {user} from admins?";
|
||||
"lng_profile_loading" = "Loading...";
|
||||
"lng_profile_photos#one" = "{count} photo";
|
||||
"lng_profile_photos#other" = "{count} photos";
|
||||
@@ -863,6 +863,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
"lng_edited" = "edited";
|
||||
"lng_edited_date" = "Edited: {date}";
|
||||
"lng_admin_badge" = "admin";
|
||||
"lng_fast_reply" = "Reply";
|
||||
"lng_cancel_edit_post_sure" = "Cancel editing?";
|
||||
"lng_cancel_edit_post_yes" = "Yes";
|
||||
"lng_cancel_edit_post_no" = "No";
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
|
||||
ProcessorArchitecture="ARCHITECTURE"
|
||||
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
|
||||
Version="1.2.4.0" />
|
||||
Version="1.2.7.0" />
|
||||
<Properties>
|
||||
<DisplayName>Telegram Desktop</DisplayName>
|
||||
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>
|
||||
|
||||
@@ -34,8 +34,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 1,2,4,0
|
||||
PRODUCTVERSION 1,2,4,0
|
||||
FILEVERSION 1,2,7,0
|
||||
PRODUCTVERSION 1,2,7,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -52,10 +52,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Telegram Messenger LLP"
|
||||
VALUE "FileDescription", "Telegram Desktop"
|
||||
VALUE "FileVersion", "1.2.4.0"
|
||||
VALUE "FileVersion", "1.2.7.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2017"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "1.2.4.0"
|
||||
VALUE "ProductVersion", "1.2.7.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -25,8 +25,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 1,2,4,0
|
||||
PRODUCTVERSION 1,2,4,0
|
||||
FILEVERSION 1,2,7,0
|
||||
PRODUCTVERSION 1,2,7,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -43,10 +43,10 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Telegram Messenger LLP"
|
||||
VALUE "FileDescription", "Telegram Desktop Updater"
|
||||
VALUE "FileVersion", "1.2.4.0"
|
||||
VALUE "FileVersion", "1.2.7.0"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2014-2017"
|
||||
VALUE "ProductName", "Telegram Desktop"
|
||||
VALUE "ProductVersion", "1.2.4.0"
|
||||
VALUE "ProductVersion", "1.2.7.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
||||
@@ -38,7 +38,6 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "storage/localstorage.h"
|
||||
#include "auth_session.h"
|
||||
#include "boxes/confirm_box.h"
|
||||
#include "window/themes/window_theme.h"
|
||||
#include "window/notifications_manager.h"
|
||||
#include "chat_helpers/message_field.h"
|
||||
#include "chat_helpers/stickers.h"
|
||||
@@ -144,64 +143,14 @@ ApiWrap::ApiWrap(not_null<AuthSession*> session)
|
||||
, _fileLoader(std::make_unique<TaskQueue>(kFileLoaderQueueStopTimeout)) {
|
||||
}
|
||||
|
||||
void ApiWrap::start() {
|
||||
Window::Theme::Background()->start();
|
||||
requestAppChangelogs();
|
||||
}
|
||||
|
||||
void ApiWrap::requestAppChangelogs() {
|
||||
auto oldAppVersion = Local::oldMapVersion();
|
||||
if (oldAppVersion > 0 && oldAppVersion < AppVersion) {
|
||||
_changelogSubscription = subscribe(_session->data().moreChatsLoaded(), [this, oldAppVersion] {
|
||||
auto oldVersionString = qsl("%1.%2.%3").arg(oldAppVersion / 1000000).arg((oldAppVersion % 1000000) / 1000).arg(oldAppVersion % 1000);
|
||||
request(MTPhelp_GetAppChangelog(MTP_string(oldVersionString))).done([this, oldAppVersion](const MTPUpdates &result) {
|
||||
applyUpdates(result);
|
||||
|
||||
auto resultEmpty = true;
|
||||
switch (result.type()) {
|
||||
case mtpc_updateShortMessage:
|
||||
case mtpc_updateShortChatMessage:
|
||||
case mtpc_updateShort: resultEmpty = false; break;
|
||||
case mtpc_updatesCombined: resultEmpty = result.c_updatesCombined().vupdates.v.isEmpty(); break;
|
||||
case mtpc_updates: resultEmpty = result.c_updates().vupdates.v.isEmpty(); break;
|
||||
case mtpc_updatesTooLong:
|
||||
case mtpc_updateShortSentMessage: LOG(("API Error: Bad updates type in app changelog.")); break;
|
||||
}
|
||||
if (resultEmpty) {
|
||||
addLocalChangelogs(oldAppVersion);
|
||||
}
|
||||
}).send();
|
||||
unsubscribe(base::take(_changelogSubscription));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void ApiWrap::addLocalChangelogs(int oldAppVersion) {
|
||||
auto addedSome = false;
|
||||
auto addLocalChangelog = [this, &addedSome](const QString &text) {
|
||||
auto textWithEntities = TextWithEntities { text };
|
||||
TextUtilities::ParseEntities(textWithEntities, TextParseLinks);
|
||||
App::wnd()->serviceNotification(textWithEntities, MTP_messageMediaEmpty(), unixtime());
|
||||
addedSome = true;
|
||||
};
|
||||
if (cAlphaVersion() || cBetaVersion()) {
|
||||
auto addLocalAlphaChangelog = [this, oldAppVersion, addLocalChangelog](int changeVersion, const char *changes) {
|
||||
if (oldAppVersion < changeVersion) {
|
||||
auto changeVersionString = QString::number(changeVersion / 1000000) + '.' + QString::number((changeVersion % 1000000) / 1000) + ((changeVersion % 1000) ? ('.' + QString::number(changeVersion % 1000)) : QString());
|
||||
auto text = qsl("New in version %1:\n\n").arg(changeVersionString) + QString::fromUtf8(changes).trimmed();
|
||||
addLocalChangelog(text);
|
||||
}
|
||||
};
|
||||
addLocalAlphaChangelog(1001024, "\xE2\x80\x94 Radically improved navigation. New side panel on the right with quick access to shared media and group members.\n\xE2\x80\x94 Pinned Messages. If you are a channel admin, pin messages to focus your subscribers\xE2\x80\x99 attention on important announcements.\n\xE2\x80\x94 Also supported clearing history in supergroups and added a host of minor improvements.");
|
||||
addLocalAlphaChangelog(1001026, "\xE2\x80\x94 Admin badges in supergroup messages.\n\xE2\x80\x94 Fix crashing on launch in OS X 10.6.\n\xE2\x80\x94 Bug fixes and other minor improvements.");
|
||||
addLocalAlphaChangelog(1001027, "\xE2\x80\x94 Saved Messages. Bookmark messages by forwarding them to \xE2\x80\x9C""Saved Messages\xE2\x80\x9D. Access them from the Chats list or from the side menu.");
|
||||
addLocalAlphaChangelog(1002002, "\xE2\x80\x94 Grouped photos and videos are displayed as albums.");
|
||||
addLocalAlphaChangelog(1002004, "\xE2\x80\x94 Group media into an album when sharing multiple photos and videos.\n\xE2\x80\x94 Bug fixes and other minor improvements.");
|
||||
}
|
||||
if (!addedSome) {
|
||||
auto text = lng_new_version_wrap(lt_version, str_const_toString(AppVersionStr), lt_changes, lang(lng_new_version_minor), lt_link, qsl("https://desktop.telegram.org/changelog")).trimmed();
|
||||
addLocalChangelog(text);
|
||||
}
|
||||
void ApiWrap::requestChangelog(
|
||||
const QString &sinceVersion,
|
||||
base::lambda<void(const MTPUpdates &result)> callback) {
|
||||
request(MTPhelp_GetAppChangelog(
|
||||
MTP_string(sinceVersion)
|
||||
)).done(
|
||||
callback
|
||||
).send();
|
||||
}
|
||||
|
||||
void ApiWrap::applyUpdates(
|
||||
|
||||
@@ -56,7 +56,6 @@ class ApiWrap : private MTP::Sender, private base::Subscriber {
|
||||
public:
|
||||
ApiWrap(not_null<AuthSession*> session);
|
||||
|
||||
void start();
|
||||
void applyUpdates(const MTPUpdates &updates, uint64 sentMessageRandomId = 0);
|
||||
|
||||
using RequestMessageDataCallback = base::lambda<void(ChannelData*, MsgId)>;
|
||||
@@ -70,6 +69,10 @@ public:
|
||||
void requestAdmins(not_null<ChannelData*> channel);
|
||||
void requestParticipantsCountDelayed(not_null<ChannelData*> channel);
|
||||
|
||||
void requestChangelog(
|
||||
const QString &sinceVersion,
|
||||
base::lambda<void(const MTPUpdates &result)> callback);
|
||||
|
||||
void requestChannelMembersForAdd(
|
||||
not_null<ChannelData*> channel,
|
||||
base::lambda<void(const MTPchannels_ChannelParticipants&)> callback);
|
||||
@@ -234,8 +237,6 @@ private:
|
||||
using MessageDataRequests = QMap<MsgId, MessageDataRequest>;
|
||||
using SharedMediaType = Storage::SharedMediaType;
|
||||
|
||||
void requestAppChangelogs();
|
||||
void addLocalChangelogs(int oldAppVersion);
|
||||
void updatesReceived(const MTPUpdates &updates);
|
||||
void checkQuitPreventFinished();
|
||||
|
||||
@@ -344,7 +345,6 @@ private:
|
||||
uint64 randomId);
|
||||
|
||||
not_null<AuthSession*> _session;
|
||||
mtpRequestId _changelogSubscription = 0;
|
||||
|
||||
MessageDataRequests _messageDataRequests;
|
||||
QMap<ChannelData*, MessageDataRequests> _channelMessageDataRequests;
|
||||
|
||||
@@ -1290,7 +1290,23 @@ namespace {
|
||||
}
|
||||
|
||||
WebPageData *feedWebPage(const MTPDwebPagePending &webpage, WebPageData *convert) {
|
||||
return App::webPageSet(webpage.vid.v, convert, QString(), QString(), QString(), QString(), QString(), TextWithEntities(), nullptr, nullptr, 0, QString(), webpage.vdate.v);
|
||||
constexpr auto kDefaultPendingTimeout = 60;
|
||||
return App::webPageSet(
|
||||
webpage.vid.v,
|
||||
convert,
|
||||
QString(),
|
||||
QString(),
|
||||
QString(),
|
||||
QString(),
|
||||
QString(),
|
||||
TextWithEntities(),
|
||||
nullptr,
|
||||
nullptr,
|
||||
0,
|
||||
QString(),
|
||||
webpage.vdate.v
|
||||
? webpage.vdate.v
|
||||
: (unixtime() + kDefaultPendingTimeout));
|
||||
}
|
||||
|
||||
WebPageData *feedWebPage(const MTPWebPage &webpage) {
|
||||
@@ -1603,9 +1619,9 @@ namespace {
|
||||
const TextWithEntities &description,
|
||||
PhotoData *photo,
|
||||
DocumentData *document,
|
||||
int32 duration,
|
||||
int duration,
|
||||
const QString &author,
|
||||
int32 pendingTill) {
|
||||
int pendingTill) {
|
||||
if (convert) {
|
||||
if (convert->id != webPage) {
|
||||
const auto i = webPagesData.find(convert->id);
|
||||
@@ -1614,23 +1630,18 @@ namespace {
|
||||
}
|
||||
convert->id = webPage;
|
||||
}
|
||||
if ((convert->url.isEmpty() && !url.isEmpty()) || (convert->pendingTill && convert->pendingTill != pendingTill && pendingTill >= -1)) {
|
||||
convert->type = toWebPageType(type);
|
||||
convert->url = TextUtilities::Clean(url);
|
||||
convert->displayUrl = TextUtilities::Clean(displayUrl);
|
||||
convert->siteName = TextUtilities::Clean(siteName);
|
||||
convert->title = TextUtilities::SingleLine(title);
|
||||
convert->description = description;
|
||||
convert->photo = photo;
|
||||
convert->document = document;
|
||||
convert->duration = duration;
|
||||
convert->author = TextUtilities::Clean(author);
|
||||
if (convert->pendingTill > 0 && pendingTill <= 0) {
|
||||
Auth().api().clearWebPageRequest(convert);
|
||||
}
|
||||
convert->pendingTill = pendingTill;
|
||||
if (App::main()) App::main()->webPageUpdated(convert);
|
||||
}
|
||||
convert->applyChanges(
|
||||
type,
|
||||
url,
|
||||
displayUrl,
|
||||
siteName,
|
||||
title,
|
||||
description,
|
||||
photo,
|
||||
document,
|
||||
duration,
|
||||
author,
|
||||
pendingTill);
|
||||
}
|
||||
const auto i = webPagesData.constFind(webPage);
|
||||
WebPageData *result;
|
||||
@@ -1638,7 +1649,19 @@ namespace {
|
||||
if (convert) {
|
||||
result = convert;
|
||||
} else {
|
||||
result = new WebPageData(webPage, toWebPageType(type), url, displayUrl, siteName, title, description, document, photo, duration, author, (pendingTill >= -1) ? pendingTill : -1);
|
||||
result = new WebPageData(
|
||||
webPage,
|
||||
toWebPageType(type),
|
||||
url,
|
||||
displayUrl,
|
||||
siteName,
|
||||
title,
|
||||
description,
|
||||
document,
|
||||
photo,
|
||||
duration,
|
||||
author,
|
||||
(pendingTill >= -1) ? pendingTill : -1);
|
||||
if (pendingTill > 0) {
|
||||
Auth().api().requestWebPageDelayed(result);
|
||||
}
|
||||
@@ -1647,23 +1670,18 @@ namespace {
|
||||
} else {
|
||||
result = i.value();
|
||||
if (result != convert) {
|
||||
if ((result->url.isEmpty() && !url.isEmpty()) || (result->pendingTill && result->pendingTill != pendingTill && pendingTill >= -1)) {
|
||||
result->type = toWebPageType(type);
|
||||
result->url = TextUtilities::Clean(url);
|
||||
result->displayUrl = TextUtilities::Clean(displayUrl);
|
||||
result->siteName = TextUtilities::Clean(siteName);
|
||||
result->title = TextUtilities::SingleLine(title);
|
||||
result->description = description;
|
||||
result->photo = photo;
|
||||
result->document = document;
|
||||
result->duration = duration;
|
||||
result->author = TextUtilities::Clean(author);
|
||||
if (result->pendingTill > 0 && pendingTill <= 0) {
|
||||
Auth().api().clearWebPageRequest(result);
|
||||
}
|
||||
result->pendingTill = pendingTill;
|
||||
if (App::main()) App::main()->webPageUpdated(result);
|
||||
}
|
||||
result->applyChanges(
|
||||
type,
|
||||
url,
|
||||
displayUrl,
|
||||
siteName,
|
||||
title,
|
||||
description,
|
||||
photo,
|
||||
document,
|
||||
duration,
|
||||
author,
|
||||
pendingTill);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
|
||||
@@ -133,13 +133,52 @@ namespace App {
|
||||
PeerData *peerByName(const QString &username);
|
||||
QString peerName(const PeerData *peer, bool forDialogs = false);
|
||||
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);
|
||||
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 version, 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 &type, const QString &url, const QString &displayUrl, const QString &siteName, const QString &title, const TextWithEntities &description, PhotoData *photo, DocumentData *doc, int32 duration, const QString &author, int32 pendingTill);
|
||||
WebPageData *webPageSet(
|
||||
const WebPageId &webPage,
|
||||
WebPageData *convert,
|
||||
const QString &type,
|
||||
const QString &url,
|
||||
const QString &displayUrl,
|
||||
const QString &siteName,
|
||||
const QString &title,
|
||||
const TextWithEntities &description,
|
||||
PhotoData *photo,
|
||||
DocumentData *document,
|
||||
int duration,
|
||||
const QString &author,
|
||||
int pendingTill);
|
||||
GameData *game(const GameId &game);
|
||||
GameData *gameSet(const GameId &game, GameData *convert, const uint64 &accessHash, const QString &shortName, const QString &title, const QString &description, PhotoData *photo, DocumentData *doc);
|
||||
GameData *gameSet(
|
||||
const GameId &game,
|
||||
GameData *convert,
|
||||
const uint64 &accessHash,
|
||||
const QString &shortName,
|
||||
const QString &title,
|
||||
const QString &description,
|
||||
PhotoData *photo,
|
||||
DocumentData *document);
|
||||
LocationData *location(const LocationCoords &coords);
|
||||
void forgetMedia();
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
|
||||
#include "apiwrap.h"
|
||||
#include "messenger.h"
|
||||
#include "core/changelogs.h"
|
||||
#include "storage/file_download.h"
|
||||
#include "storage/file_upload.h"
|
||||
#include "storage/localstorage.h"
|
||||
@@ -29,6 +30,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "storage/serialize_common.h"
|
||||
#include "history/history_item_components.h"
|
||||
#include "window/notifications_manager.h"
|
||||
#include "window/themes/window_theme.h"
|
||||
#include "platform/platform_specific.h"
|
||||
#include "calls/calls_instance.h"
|
||||
#include "window/section_widget.h"
|
||||
@@ -408,8 +410,10 @@ AuthSession::AuthSession(UserId userId)
|
||||
, _downloader(std::make_unique<Storage::Downloader>())
|
||||
, _uploader(std::make_unique<Storage::Uploader>())
|
||||
, _storage(std::make_unique<Storage::Facade>())
|
||||
, _notifications(std::make_unique<Window::Notifications::System>(this)) {
|
||||
, _notifications(std::make_unique<Window::Notifications::System>(this))
|
||||
, _changelogs(Core::Changelogs::Create(this)) {
|
||||
Expects(_userId != 0);
|
||||
|
||||
_saveDataTimer.setCallback([this] {
|
||||
Local::writeUserSettings();
|
||||
});
|
||||
@@ -417,7 +421,7 @@ AuthSession::AuthSession(UserId userId)
|
||||
_shouldLockAt = 0;
|
||||
notifications().updateAll();
|
||||
});
|
||||
_api->start();
|
||||
Window::Theme::Background()->start();
|
||||
}
|
||||
|
||||
bool AuthSession::Exists() {
|
||||
|
||||
@@ -50,6 +50,10 @@ namespace ChatHelpers {
|
||||
enum class SelectorTab;
|
||||
} // namespace ChatHelpers
|
||||
|
||||
namespace Core {
|
||||
class Changelogs;
|
||||
} // namespace Core
|
||||
|
||||
class AuthSessionData final {
|
||||
public:
|
||||
base::Variable<bool> &contactsLoaded() {
|
||||
@@ -414,5 +418,6 @@ private:
|
||||
const std::unique_ptr<Storage::Uploader> _uploader;
|
||||
const std::unique_ptr<Storage::Facade> _storage;
|
||||
const std::unique_ptr<Window::Notifications::System> _notifications;
|
||||
const std::unique_ptr<Core::Changelogs> _changelogs;
|
||||
|
||||
};
|
||||
|
||||
@@ -1,393 +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-2017 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#include "base/task_queue.h"
|
||||
|
||||
#include <thread>
|
||||
#include <condition_variable>
|
||||
|
||||
namespace base {
|
||||
namespace {
|
||||
|
||||
auto MainThreadId = std::this_thread::get_id();
|
||||
const auto MaxThreadsCount = qMax(std::thread::hardware_concurrency(), 2U);
|
||||
|
||||
} // namespace
|
||||
|
||||
class TaskQueue::TaskQueueList {
|
||||
public:
|
||||
TaskQueueList();
|
||||
|
||||
void Register(TaskQueue *queue);
|
||||
void Unregister(TaskQueue *queue);
|
||||
bool IsInList(TaskQueue *queue) const;
|
||||
void Clear();
|
||||
bool Empty(int list_index_) const;
|
||||
TaskQueue *TakeFirst(int list_index_);
|
||||
|
||||
private:
|
||||
void Insert(TaskQueue *queue, int list_index_);
|
||||
void Remove(TaskQueue *queue, int list_index_);
|
||||
|
||||
TaskQueue *Tail() { return &tail_; }
|
||||
const TaskQueue *Tail() const { return &tail_; }
|
||||
|
||||
TaskQueue tail_ = { Type::Special, Priority::Normal };
|
||||
TaskQueue *(lists_[kQueuesListsCount]);
|
||||
|
||||
};
|
||||
|
||||
class TaskQueue::TaskThreadPool {
|
||||
struct Private {
|
||||
};
|
||||
|
||||
public:
|
||||
TaskThreadPool(const Private &) { }
|
||||
static const std::shared_ptr<TaskThreadPool> &Instance();
|
||||
|
||||
void AddQueueTask(TaskQueue *queue, Task &&task);
|
||||
void RemoveQueue(TaskQueue *queue);
|
||||
|
||||
~TaskThreadPool();
|
||||
|
||||
private:
|
||||
void ThreadFunction();
|
||||
|
||||
std::vector<std::thread> threads_;
|
||||
QMutex queues_mutex_;
|
||||
|
||||
// queues_mutex_ must be locked when working with the list.
|
||||
TaskQueueList queue_list_;
|
||||
|
||||
QWaitCondition thread_condition_;
|
||||
bool stopped_ = false;
|
||||
int tasks_in_process_ = 0;
|
||||
int background_tasks_in_process_ = 0;
|
||||
|
||||
};
|
||||
|
||||
TaskQueue::TaskQueueList::TaskQueueList() {
|
||||
for (auto &list : lists_) {
|
||||
list = &tail_;
|
||||
}
|
||||
}
|
||||
|
||||
void TaskQueue::TaskQueueList::Register(TaskQueue *queue) {
|
||||
Assert(!queue->SerialTaskInProcess());
|
||||
|
||||
Insert(queue, kAllQueuesList);
|
||||
if (queue->priority_ == Priority::Normal) {
|
||||
Insert(queue, kOnlyNormalQueuesList);
|
||||
}
|
||||
}
|
||||
|
||||
void TaskQueue::TaskQueueList::Unregister(TaskQueue *queue) {
|
||||
Remove(queue, kAllQueuesList);
|
||||
if (queue->priority_ == Priority::Normal) {
|
||||
Remove(queue, kOnlyNormalQueuesList);
|
||||
}
|
||||
}
|
||||
|
||||
void TaskQueue::TaskQueueList::Insert(TaskQueue *queue, int list_index_) {
|
||||
Assert(list_index_ < kQueuesListsCount);
|
||||
|
||||
auto tail = Tail();
|
||||
if (lists_[list_index_] == tail) {
|
||||
lists_[list_index_] = queue;
|
||||
}
|
||||
|
||||
auto &list_entry = queue->list_entries_[list_index_];
|
||||
Assert(list_entry.after == nullptr);
|
||||
if ((list_entry.before = tail->list_entries_[list_index_].before)) {
|
||||
list_entry.before->list_entries_[list_index_].after = queue;
|
||||
}
|
||||
list_entry.after = tail;
|
||||
tail->list_entries_[list_index_].before = queue;
|
||||
}
|
||||
|
||||
void TaskQueue::TaskQueueList::Remove(TaskQueue *queue, int list_index_) {
|
||||
Assert(list_index_ < kQueuesListsCount);
|
||||
|
||||
auto &list_entry = queue->list_entries_[list_index_];
|
||||
Assert(list_entry.after != nullptr);
|
||||
if (lists_[list_index_] == queue) {
|
||||
lists_[list_index_] = list_entry.after;
|
||||
} else {
|
||||
Assert(list_entry.before != nullptr);
|
||||
list_entry.before->list_entries_[list_index_].after = list_entry.after;
|
||||
}
|
||||
list_entry.after->list_entries_[list_index_].before = list_entry.before;
|
||||
list_entry.before = list_entry.after = nullptr;
|
||||
}
|
||||
|
||||
bool TaskQueue::TaskQueueList::IsInList(TaskQueue *queue) const {
|
||||
if (queue->list_entries_[kAllQueuesList].after) {
|
||||
return true;
|
||||
}
|
||||
Assert(queue->list_entries_[kOnlyNormalQueuesList].after == nullptr);
|
||||
return false;
|
||||
}
|
||||
|
||||
void TaskQueue::TaskQueueList::Clear() {
|
||||
auto tail = Tail();
|
||||
for (int i = 0; i < kQueuesListsCount; ++i) {
|
||||
for (auto j = lists_[i], next = j; j != tail; j = next) {
|
||||
auto &list_entry = j->list_entries_[i];
|
||||
next = list_entry.after;
|
||||
list_entry.before = list_entry.after = nullptr;
|
||||
}
|
||||
lists_[i] = tail;
|
||||
}
|
||||
}
|
||||
|
||||
bool TaskQueue::TaskQueueList::Empty(int list_index_) const {
|
||||
Assert(list_index_ < kQueuesListsCount);
|
||||
|
||||
auto list = lists_[list_index_];
|
||||
Assert(list != nullptr);
|
||||
return (list->list_entries_[list_index_].after == nullptr);
|
||||
}
|
||||
|
||||
TaskQueue *TaskQueue::TaskQueueList::TakeFirst(int list_index_) {
|
||||
Assert(!Empty(list_index_));
|
||||
|
||||
auto queue = lists_[list_index_];
|
||||
Unregister(queue);
|
||||
// log_msgs.push_back("Unregistered from list in TakeFirst");
|
||||
return queue;
|
||||
}
|
||||
|
||||
void TaskQueue::TaskThreadPool::AddQueueTask(TaskQueue *queue, Task &&task) {
|
||||
QMutexLocker lock(&queues_mutex_);
|
||||
|
||||
queue->tasks_.push_back(std::move(task));
|
||||
auto list_was_empty = queue_list_.Empty(kAllQueuesList);
|
||||
auto threads_count = threads_.size();
|
||||
auto all_threads_processing = (threads_count == tasks_in_process_);
|
||||
auto some_threads_are_vacant = !all_threads_processing && list_was_empty;
|
||||
auto will_create_thread = !some_threads_are_vacant && (threads_count < MaxThreadsCount);
|
||||
|
||||
if (!queue->SerialTaskInProcess()) {
|
||||
if (!queue_list_.IsInList(queue)) {
|
||||
queue_list_.Register(queue);
|
||||
}
|
||||
}
|
||||
if (will_create_thread) {
|
||||
threads_.emplace_back([this]() {
|
||||
ThreadFunction();
|
||||
});
|
||||
} else if (some_threads_are_vacant) {
|
||||
Assert(threads_count > tasks_in_process_);
|
||||
thread_condition_.wakeOne();
|
||||
}
|
||||
}
|
||||
|
||||
void TaskQueue::TaskThreadPool::RemoveQueue(TaskQueue *queue) {
|
||||
QMutexLocker lock(&queues_mutex_);
|
||||
if (queue_list_.IsInList(queue)) {
|
||||
queue_list_.Unregister(queue);
|
||||
}
|
||||
if (queue->destroyed_flag_) {
|
||||
*queue->destroyed_flag_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
TaskQueue::TaskThreadPool::~TaskThreadPool() {
|
||||
{
|
||||
QMutexLocker lock(&queues_mutex_);
|
||||
queue_list_.Clear();
|
||||
stopped_ = true;
|
||||
}
|
||||
thread_condition_.wakeAll();
|
||||
for (auto &thread : threads_) {
|
||||
thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
const std::shared_ptr<TaskQueue::TaskThreadPool> &TaskQueue::TaskThreadPool::Instance() { // static
|
||||
static auto Pool = std::make_shared<TaskThreadPool>(Private());
|
||||
return Pool;
|
||||
}
|
||||
|
||||
void TaskQueue::TaskThreadPool::ThreadFunction() {
|
||||
// Flag marking that the previous processed task was
|
||||
// with a Background priority. We count all the background
|
||||
// tasks being processed.
|
||||
bool background_task = false;
|
||||
|
||||
// Saved serial queue pointer. When we process a serial
|
||||
// queue task we don't return the queue to the list until
|
||||
// the task is processed and we return it on the next cycle.
|
||||
TaskQueue *serial_queue = nullptr;
|
||||
bool serial_queue_destroyed = false;
|
||||
bool task_was_processed = false;
|
||||
while (true) {
|
||||
Task task;
|
||||
{
|
||||
QMutexLocker lock(&queues_mutex_);
|
||||
|
||||
// Finish the previous task processing.
|
||||
if (task_was_processed) {
|
||||
--tasks_in_process_;
|
||||
}
|
||||
if (background_task) {
|
||||
--background_tasks_in_process_;
|
||||
background_task = false;
|
||||
}
|
||||
if (serial_queue) {
|
||||
if (!serial_queue_destroyed) {
|
||||
serial_queue->destroyed_flag_ = nullptr;
|
||||
if (!serial_queue->tasks_.empty()) {
|
||||
queue_list_.Register(serial_queue);
|
||||
}
|
||||
}
|
||||
serial_queue = nullptr;
|
||||
serial_queue_destroyed = false;
|
||||
}
|
||||
|
||||
// Wait for a task to appear in the queues list.
|
||||
while (queue_list_.Empty(kAllQueuesList)) {
|
||||
if (stopped_) {
|
||||
return;
|
||||
}
|
||||
thread_condition_.wait(&queues_mutex_);
|
||||
}
|
||||
|
||||
// Select a task we will be processing.
|
||||
auto processing_background = (background_tasks_in_process_ > 0);
|
||||
auto take_only_normal = processing_background && !queue_list_.Empty(kOnlyNormalQueuesList);
|
||||
auto take_from_list_ = take_only_normal ? kOnlyNormalQueuesList : kAllQueuesList;
|
||||
auto queue = queue_list_.TakeFirst(take_from_list_);
|
||||
|
||||
Assert(!queue->tasks_.empty());
|
||||
|
||||
task = std::move(queue->tasks_.front());
|
||||
queue->tasks_.pop_front();
|
||||
|
||||
if (queue->type_ == Type::Serial) {
|
||||
// Serial queues are returned in the list for processing
|
||||
// only after the task is finished.
|
||||
serial_queue = queue;
|
||||
Assert(serial_queue->destroyed_flag_ == nullptr);
|
||||
serial_queue->destroyed_flag_ = &serial_queue_destroyed;
|
||||
} else if (!queue->tasks_.empty()) {
|
||||
queue_list_.Register(queue);
|
||||
}
|
||||
|
||||
++tasks_in_process_;
|
||||
task_was_processed = true;
|
||||
if (queue->priority_ == Priority::Background) {
|
||||
++background_tasks_in_process_;
|
||||
background_task = true;
|
||||
}
|
||||
}
|
||||
|
||||
task();
|
||||
}
|
||||
}
|
||||
|
||||
TaskQueue::TaskQueue(Type type, Priority priority)
|
||||
: type_(type)
|
||||
, priority_(priority) {
|
||||
if (type_ != Type::Main && type_ != Type::Special) {
|
||||
weak_thread_pool_ = TaskThreadPool::Instance();
|
||||
}
|
||||
}
|
||||
|
||||
TaskQueue::~TaskQueue() {
|
||||
if (type_ != Type::Main && type_ != Type::Special) {
|
||||
if (auto thread_pool = weak_thread_pool_.lock()) {
|
||||
thread_pool->RemoveQueue(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TaskQueue::Put(Task &&task) {
|
||||
if (type_ == Type::Main) {
|
||||
QMutexLocker lock(&tasks_mutex_);
|
||||
tasks_.push_back(std::move(task));
|
||||
|
||||
Sandbox::MainThreadTaskAdded();
|
||||
} else {
|
||||
Assert(type_ != Type::Special);
|
||||
TaskThreadPool::Instance()->AddQueueTask(this, std::move(task));
|
||||
}
|
||||
}
|
||||
|
||||
void TaskQueue::ProcessMainTasks() { // static
|
||||
Assert(std::this_thread::get_id() == MainThreadId);
|
||||
|
||||
while (ProcessOneMainTask()) {
|
||||
}
|
||||
}
|
||||
|
||||
void TaskQueue::ProcessMainTasks(TimeMs max_time_spent) { // static
|
||||
Assert(std::this_thread::get_id() == MainThreadId);
|
||||
|
||||
auto start_time = getms();
|
||||
while (ProcessOneMainTask()) {
|
||||
if (getms() >= start_time + max_time_spent) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool TaskQueue::ProcessOneMainTask() { // static
|
||||
Task task;
|
||||
{
|
||||
QMutexLocker lock(&Main().tasks_mutex_);
|
||||
auto &tasks = Main().tasks_;
|
||||
if (tasks.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
task = std::move(tasks.front());
|
||||
tasks.pop_front();
|
||||
}
|
||||
|
||||
task();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TaskQueue::IsMyThread() const {
|
||||
if (type_ == Type::Main) {
|
||||
return (std::this_thread::get_id() == MainThreadId);
|
||||
}
|
||||
Assert(type_ != Type::Special);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Default queues.
|
||||
TaskQueue &TaskQueue::Main() { // static
|
||||
static TaskQueue MainQueue { Type::Main, Priority::Normal };
|
||||
return MainQueue;
|
||||
}
|
||||
|
||||
TaskQueue &TaskQueue::Normal() { // static
|
||||
static TaskQueue NormalQueue { Type::Concurrent, Priority::Normal };
|
||||
return NormalQueue;
|
||||
}
|
||||
|
||||
TaskQueue &TaskQueue::Background() { // static
|
||||
static TaskQueue BackgroundQueue { Type::Concurrent, Priority::Background };
|
||||
return BackgroundQueue;
|
||||
}
|
||||
|
||||
} // namespace base
|
||||
@@ -1,102 +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-2017 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace base {
|
||||
|
||||
using Task = lambda_once<void()>;
|
||||
|
||||
// An attempt to create/use a TaskQueue or one of the default queues
|
||||
// after the main() has returned leads to an undefined behaviour.
|
||||
class TaskQueue {
|
||||
enum class Type {
|
||||
Main, // Unique queue for main thread tasks.
|
||||
Serial,
|
||||
Concurrent,
|
||||
Special, // Unique special queue for thread pool lists terminal item.
|
||||
};
|
||||
|
||||
public:
|
||||
enum class Priority {
|
||||
Normal,
|
||||
Background,
|
||||
};
|
||||
|
||||
// Creating custom serial queues.
|
||||
TaskQueue(Priority priority) : TaskQueue(Type::Serial, priority) {
|
||||
}
|
||||
|
||||
// Default main and two concurrent queues.
|
||||
static TaskQueue &Main();
|
||||
static TaskQueue &Normal();
|
||||
static TaskQueue &Background();
|
||||
|
||||
void Put(Task &&task);
|
||||
|
||||
static void ProcessMainTasks();
|
||||
static void ProcessMainTasks(TimeMs max_time_spent);
|
||||
|
||||
~TaskQueue();
|
||||
|
||||
private:
|
||||
static bool ProcessOneMainTask();
|
||||
|
||||
TaskQueue(Type type, Priority priority);
|
||||
|
||||
bool IsMyThread() const;
|
||||
bool SerialTaskInProcess() const {
|
||||
return (destroyed_flag_ != nullptr);
|
||||
}
|
||||
|
||||
const Type type_;
|
||||
const Priority priority_;
|
||||
|
||||
std::deque<Task> tasks_;
|
||||
QMutex tasks_mutex_; // Only for the main queue.
|
||||
|
||||
// Only for the other queues, not main.
|
||||
class TaskThreadPool;
|
||||
std::weak_ptr<TaskThreadPool> weak_thread_pool_;
|
||||
|
||||
class TaskQueueList;
|
||||
|
||||
struct TaskQueueListEntry {
|
||||
TaskQueue *before = nullptr;
|
||||
TaskQueue *after = nullptr;
|
||||
};
|
||||
|
||||
// Thread pool queues linked list.
|
||||
static constexpr int kAllQueuesList = 0;
|
||||
|
||||
// Thread pool queues linked list with excluded Background queues.
|
||||
static constexpr int kOnlyNormalQueuesList = 1;
|
||||
|
||||
static constexpr int kQueuesListsCount = 2;
|
||||
TaskQueueListEntry list_entries_[kQueuesListsCount];
|
||||
|
||||
// Only for Serial queues: non-null value means a task is currently processed.
|
||||
bool *destroyed_flag_ = nullptr;
|
||||
|
||||
};
|
||||
|
||||
} // namespace base
|
||||
@@ -280,6 +280,55 @@ weak_ptr<T> make_weak(const std::weak_ptr<T> &value) {
|
||||
|
||||
} // namespace base
|
||||
|
||||
namespace crl {
|
||||
|
||||
template <typename T, typename Enable>
|
||||
struct guard_traits;
|
||||
|
||||
template <typename T>
|
||||
struct guard_traits<base::weak_ptr<T>, void> {
|
||||
static base::weak_ptr<T> create(const base::weak_ptr<T> &value) {
|
||||
return value;
|
||||
}
|
||||
static base::weak_ptr<T> create(base::weak_ptr<T> &&value) {
|
||||
return std::move(value);
|
||||
}
|
||||
static bool check(const base::weak_ptr<T> &guard) {
|
||||
return guard.get() != nullptr;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct guard_traits<
|
||||
T*,
|
||||
std::enable_if_t<
|
||||
std::is_base_of_v<base::has_weak_ptr, std::remove_cv_t<T>>>> {
|
||||
static base::weak_ptr<T> create(T *value) {
|
||||
return value;
|
||||
}
|
||||
static bool check(const base::weak_ptr<T> &guard) {
|
||||
return guard.get() != nullptr;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct guard_traits<
|
||||
gsl::not_null<T*>,
|
||||
std::enable_if_t<
|
||||
std::is_base_of_v<base::has_weak_ptr, std::remove_cv_t<T>>>> {
|
||||
static base::weak_ptr<T> create(gsl::not_null<T*> value) {
|
||||
return value.get();
|
||||
}
|
||||
static bool check(const base::weak_ptr<T> &guard) {
|
||||
return guard.get() != nullptr;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
} // namespace crl
|
||||
|
||||
#ifdef QT_VERSION
|
||||
template <typename Lambda>
|
||||
inline void InvokeQueued(const base::has_weak_ptr *context, Lambda &&lambda) {
|
||||
|
||||
@@ -36,6 +36,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "ui/toast/toast.h"
|
||||
#include "ui/special_buttons.h"
|
||||
#include "ui/text_options.h"
|
||||
#include "mainwidget.h"
|
||||
#include "mainwindow.h"
|
||||
#include "apiwrap.h"
|
||||
@@ -1378,8 +1379,15 @@ RevokePublicLinkBox::Inner::Inner(QWidget *parent, base::lambda<void()> revokeCa
|
||||
|
||||
auto row = ChatRow(peer);
|
||||
row.peer = peer;
|
||||
row.name.setText(st::contactsNameStyle, peer->name, _textNameOptions);
|
||||
row.status.setText(st::defaultTextStyle, Messenger::Instance().createInternalLink(textcmdLink(1, peer->userName())), _textDlgOptions);
|
||||
row.name.setText(
|
||||
st::contactsNameStyle,
|
||||
peer->name,
|
||||
Ui::NameTextOptions());
|
||||
row.status.setText(
|
||||
st::defaultTextStyle,
|
||||
Messenger::Instance().createInternalLink(
|
||||
textcmdLink(1, peer->userName())),
|
||||
Ui::DialogTextOptions());
|
||||
_rows.push_back(std::move(row));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "boxes/edit_caption_box.h"
|
||||
|
||||
#include "ui/widgets/input_fields.h"
|
||||
#include "ui/text_options.h"
|
||||
#include "media/media_clip_reader.h"
|
||||
#include "history/history_media_types.h"
|
||||
#include "lang/lang_keys.h"
|
||||
@@ -106,7 +107,7 @@ EditCaptionBox::EditCaptionBox(
|
||||
_name.setText(
|
||||
st::semiboldTextStyle,
|
||||
nameString,
|
||||
_textNameOptions);
|
||||
Ui::NameTextOptions());
|
||||
_status = formatSizeText(doc->size);
|
||||
_statusw = qMax(
|
||||
_name.maxWidth(),
|
||||
|
||||
@@ -24,10 +24,11 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "ui/widgets/checkbox.h"
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "styles/style_boxes.h"
|
||||
#include "ui/text_options.h"
|
||||
#include "ui/special_buttons.h"
|
||||
#include "boxes/calendar_box.h"
|
||||
#include "data/data_peer_values.h"
|
||||
#include "styles/style_boxes.h"
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -128,7 +129,10 @@ EditParticipantBox::Inner::Inner(
|
||||
st::rightsPhotoButton)
|
||||
, _hasAdminRights(hasAdminRights) {
|
||||
_userPhoto->setPointerCursor(false);
|
||||
_userName.setText(st::rightsNameStyle, App::peerName(_user), _textNameOptions);
|
||||
_userName.setText(
|
||||
st::rightsNameStyle,
|
||||
App::peerName(_user),
|
||||
Ui::NameTextOptions());
|
||||
}
|
||||
|
||||
void EditParticipantBox::Inner::removeControl(QPointer<TWidget> widget) {
|
||||
|
||||
@@ -34,6 +34,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "ui/effects/ripple_animation.h"
|
||||
#include "ui/empty_userpic.h"
|
||||
#include "ui/wrap/slide_wrap.h"
|
||||
#include "ui/text_options.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "observer_peer.h"
|
||||
#include "storage/file_download.h"
|
||||
@@ -391,7 +392,7 @@ void PeerListRow::refreshName(const style::PeerListItem &st) {
|
||||
const auto text = _isSavedMessagesChat
|
||||
? lang(lng_saved_messages)
|
||||
: peer()->name;
|
||||
_name.setText(st.nameStyle, text, _textNameOptions);
|
||||
_name.setText(st.nameStyle, text, Ui::NameTextOptions());
|
||||
}
|
||||
|
||||
PeerListRow::~PeerListRow() = default;
|
||||
@@ -516,7 +517,7 @@ void PeerListRow::paintDisabledCheckUserpic(
|
||||
}
|
||||
|
||||
void PeerListRow::setStatusText(const QString &text) {
|
||||
_status.setText(st::defaultTextStyle, text, _textNameOptions);
|
||||
_status.setText(st::defaultTextStyle, text, Ui::NameTextOptions());
|
||||
}
|
||||
|
||||
float64 PeerListRow::checkedRatio() {
|
||||
|
||||
@@ -168,18 +168,20 @@ void FillManageBox(
|
||||
st::infoIconAdministrators);
|
||||
}
|
||||
if (channel->canViewBanned()) {
|
||||
AddButtonWithCount(
|
||||
content,
|
||||
Lang::Viewer(lng_manage_peer_restricted_users),
|
||||
Info::Profile::RestrictedCountValue(channel)
|
||||
| ToPositiveNumberString(),
|
||||
[=] {
|
||||
ParticipantsBoxController::Start(
|
||||
controller,
|
||||
channel,
|
||||
ParticipantsBoxController::Role::Restricted);
|
||||
},
|
||||
st::infoIconRestrictedUsers);
|
||||
if (channel->isMegagroup()) {
|
||||
AddButtonWithCount(
|
||||
content,
|
||||
Lang::Viewer(lng_manage_peer_restricted_users),
|
||||
Info::Profile::RestrictedCountValue(channel)
|
||||
| ToPositiveNumberString(),
|
||||
[=] {
|
||||
ParticipantsBoxController::Start(
|
||||
controller,
|
||||
channel,
|
||||
ParticipantsBoxController::Role::Restricted);
|
||||
},
|
||||
st::infoIconRestrictedUsers);
|
||||
}
|
||||
AddButtonWithCount(
|
||||
content,
|
||||
Lang::Viewer(lng_manage_peer_banned_users),
|
||||
|
||||
@@ -32,6 +32,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "ui/widgets/scroll_area.h"
|
||||
#include "ui/wrap/fade_wrap.h"
|
||||
#include "ui/grouped_layout.h"
|
||||
#include "ui/text_options.h"
|
||||
#include "media/media_clip_reader.h"
|
||||
#include "window/window_controller.h"
|
||||
#include "styles/style_history.h"
|
||||
@@ -115,6 +116,8 @@ public:
|
||||
const Ui::GroupMediaLayout &layout);
|
||||
|
||||
void moveToLayout(const Ui::GroupMediaLayout &layout);
|
||||
void animateLayoutToInitial();
|
||||
void resetLayoutAnimation();
|
||||
|
||||
int photoHeight() const;
|
||||
|
||||
@@ -143,7 +146,7 @@ private:
|
||||
void drawSimpleFrame(Painter &p, QRect to, QSize size) const;
|
||||
|
||||
Ui::GroupMediaLayout _layout;
|
||||
QRect _fromGeometry;
|
||||
base::optional<QRect> _animateFromGeometry;
|
||||
const QImage _fullPreview;
|
||||
const int _shrinkSize = 0;
|
||||
QPixmap _albumImage;
|
||||
@@ -234,11 +237,19 @@ AlbumThumb::AlbumThumb(
|
||||
_statusWidth = st::normalFont->width(_status);
|
||||
}
|
||||
|
||||
void AlbumThumb::moveToLayout(const Ui::GroupMediaLayout &layout) {
|
||||
_fromGeometry = countRealGeometry();
|
||||
_layout = layout;
|
||||
void AlbumThumb::resetLayoutAnimation() {
|
||||
_animateFromGeometry = base::none;
|
||||
}
|
||||
|
||||
void AlbumThumb::animateLayoutToInitial() {
|
||||
_animateFromGeometry = countRealGeometry();
|
||||
_suggestedMove = 0.;
|
||||
_albumPosition = QPoint(0, 0);
|
||||
}
|
||||
|
||||
void AlbumThumb::moveToLayout(const Ui::GroupMediaLayout &layout) {
|
||||
animateLayoutToInitial();
|
||||
_layout = layout;
|
||||
|
||||
const auto width = _layout.geometry.width();
|
||||
const auto height = _layout.geometry.height();
|
||||
@@ -323,12 +334,11 @@ void AlbumThumb::paintInAlbum(
|
||||
void AlbumThumb::prepareCache(QSize size, int shrink) {
|
||||
const auto width = std::max(
|
||||
_layout.geometry.width(),
|
||||
_fromGeometry.width());
|
||||
_animateFromGeometry ? _animateFromGeometry->width() : 0);
|
||||
const auto height = std::max(
|
||||
_layout.geometry.height(),
|
||||
_fromGeometry.height());
|
||||
_animateFromGeometry ? _animateFromGeometry->height() : 0);
|
||||
const auto cacheSize = QSize(width, height) * cIntRetinaFactor();
|
||||
const auto initial = QRect(QPoint(), size);
|
||||
|
||||
if (_albumCache.width() < cacheSize.width()
|
||||
|| _albumCache.height() < cacheSize.height()) {
|
||||
@@ -337,7 +347,7 @@ void AlbumThumb::prepareCache(QSize size, int shrink) {
|
||||
_albumCache.fill(Qt::transparent);
|
||||
{
|
||||
Painter p(&_albumCache);
|
||||
const auto to = initial.marginsRemoved(
|
||||
const auto to = QRect(QPoint(), size).marginsRemoved(
|
||||
{ shrink, shrink, shrink, shrink }
|
||||
);
|
||||
drawSimpleFrame(p, to, size);
|
||||
@@ -346,7 +356,7 @@ void AlbumThumb::prepareCache(QSize size, int shrink) {
|
||||
_albumCache,
|
||||
ImageRoundRadius::Large,
|
||||
_albumCorners,
|
||||
initial);
|
||||
QRect(QPoint(), size * cIntRetinaFactor()));
|
||||
}
|
||||
|
||||
void AlbumThumb::drawSimpleFrame(Painter &p, QRect to, QSize size) const {
|
||||
@@ -543,12 +553,12 @@ QRect AlbumThumb::countRealGeometry() const {
|
||||
|
||||
QRect AlbumThumb::countCurrentGeometry(float64 progress) const {
|
||||
const auto now = countRealGeometry();
|
||||
if (progress < 1.) {
|
||||
if (_animateFromGeometry && progress < 1.) {
|
||||
return {
|
||||
anim::interpolate(_fromGeometry.x(), now.x(), progress),
|
||||
anim::interpolate(_fromGeometry.y(), now.y(), progress),
|
||||
anim::interpolate(_fromGeometry.width(), now.width(), progress),
|
||||
anim::interpolate(_fromGeometry.height(), now.height(), progress)
|
||||
anim::interpolate(_animateFromGeometry->x(), now.x(), progress),
|
||||
anim::interpolate(_animateFromGeometry->y(), now.y(), progress),
|
||||
anim::interpolate(_animateFromGeometry->width(), now.width(), progress),
|
||||
anim::interpolate(_animateFromGeometry->height(), now.height(), progress)
|
||||
};
|
||||
}
|
||||
return now;
|
||||
@@ -787,7 +797,10 @@ void SingleFilePreview::preparePreview(const Storage::PreparedFile &file) {
|
||||
const auto filepath = file.path;
|
||||
if (filepath.isEmpty()) {
|
||||
auto filename = filedialogDefaultName(qsl("image"), qsl(".png"), QString(), true);
|
||||
_nameText.setText(st::semiboldTextStyle, filename, _textNameOptions);
|
||||
_nameText.setText(
|
||||
st::semiboldTextStyle,
|
||||
filename,
|
||||
Ui::NameTextOptions());
|
||||
_statusText = qsl("%1x%2").arg(preview.width()).arg(preview.height());
|
||||
_statusWidth = qMax(_nameText.maxWidth(), st::normalFont->width(_statusText));
|
||||
_fileIsImage = true;
|
||||
@@ -814,7 +827,7 @@ void SingleFilePreview::preparePreview(const Storage::PreparedFile &file) {
|
||||
_nameText.setText(
|
||||
st::semiboldTextStyle,
|
||||
nameString,
|
||||
_textNameOptions);
|
||||
Ui::NameTextOptions());
|
||||
_statusText = formatSizeText(fileinfo.size());
|
||||
_statusWidth = qMax(
|
||||
_nameText.maxWidth(),
|
||||
@@ -1113,7 +1126,11 @@ void SendFilesBox::AlbumPreview::finishDrag() {
|
||||
|
||||
updateSizeAnimated(layout);
|
||||
} else {
|
||||
_draggedThumb->moveInAlbum(QPoint());
|
||||
for (const auto &thumb : _thumbs) {
|
||||
thumb->resetLayoutAnimation();
|
||||
}
|
||||
_draggedThumb->animateLayoutToInitial();
|
||||
_finishDragAnimation.start([=] { update(); }, 0., 1., kDragDuration);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1312,8 +1329,8 @@ SendFilesBox::SendFilesBox(
|
||||
Storage::PreparedList &&list,
|
||||
CompressConfirm compressed)
|
||||
: _list(std::move(list))
|
||||
, _compressConfirm(compressed)
|
||||
, _caption(this, st::confirmCaptionArea, FieldPlaceholder(_list)) {
|
||||
, _compressConfirmInitial(compressed)
|
||||
, _compressConfirm(compressed) {
|
||||
}
|
||||
|
||||
void SendFilesBox::initPreview(rpl::producer<int> desiredPreviewHeight) {
|
||||
@@ -1381,7 +1398,7 @@ void SendFilesBox::setupShadows(
|
||||
const auto topShadow = Ui::CreateChild<Ui::FadeShadow>(this);
|
||||
const auto bottomShadow = Ui::CreateChild<Ui::FadeShadow>(this);
|
||||
wrap->geometryValue(
|
||||
) | rpl::start_with_next([=](const QRect &geometry) {
|
||||
) | rpl::start_with_next_done([=](const QRect &geometry) {
|
||||
topShadow->resizeToWidth(geometry.width());
|
||||
topShadow->move(
|
||||
geometry.x(),
|
||||
@@ -1390,7 +1407,11 @@ void SendFilesBox::setupShadows(
|
||||
bottomShadow->move(
|
||||
geometry.x(),
|
||||
geometry.y() + geometry.height() - st::lineWidth);
|
||||
}, [t = make_weak(topShadow), b = make_weak(bottomShadow)] {
|
||||
Ui::DestroyChild(t.data());
|
||||
Ui::DestroyChild(b.data());
|
||||
}, topShadow->lifetime());
|
||||
|
||||
topShadow->toggleOn(wrap->scrollTopValue() | rpl::map(_1 > 0));
|
||||
bottomShadow->toggleOn(rpl::combine(
|
||||
wrap->scrollTopValue(),
|
||||
@@ -1405,17 +1426,7 @@ void SendFilesBox::prepare() {
|
||||
_send = addButton(langFactory(lng_send_button), [this] { send(); });
|
||||
addButton(langFactory(lng_cancel), [this] { closeBox(); });
|
||||
initSendWay();
|
||||
if (_list.files.size() == 1) {
|
||||
prepareSingleFilePreview();
|
||||
} else {
|
||||
if (_list.albumIsPossible) {
|
||||
prepareAlbumPreview();
|
||||
} else {
|
||||
auto desiredPreviewHeight = rpl::single(0);
|
||||
initPreview(std::move(desiredPreviewHeight));
|
||||
}
|
||||
}
|
||||
|
||||
preparePreview();
|
||||
subscribe(boxClosing, [this] {
|
||||
if (!_confirmed && _cancelledCallback) {
|
||||
_cancelledCallback();
|
||||
@@ -1424,15 +1435,7 @@ void SendFilesBox::prepare() {
|
||||
}
|
||||
|
||||
void SendFilesBox::initSendWay() {
|
||||
_albumVideosCount = _list.albumIsPossible
|
||||
? ranges::count(
|
||||
_list.files,
|
||||
Storage::PreparedFile::AlbumType::Video,
|
||||
[](const Storage::PreparedFile &file) { return file.type; })
|
||||
: 0;
|
||||
_albumPhotosCount = _list.albumIsPossible
|
||||
? (_list.files.size() - _albumVideosCount)
|
||||
: 0;
|
||||
refreshAlbumMediaCount();
|
||||
const auto value = [&] {
|
||||
if (_compressConfirm == CompressConfirm::None) {
|
||||
return SendFilesWay::Files;
|
||||
@@ -1456,6 +1459,38 @@ void SendFilesBox::initSendWay() {
|
||||
: SendFilesWay::Photos;
|
||||
}();
|
||||
_sendWay = std::make_shared<Ui::RadioenumGroup<SendFilesWay>>(value);
|
||||
_sendWay->setChangedCallback([this](SendFilesWay value) {
|
||||
applyAlbumOrder();
|
||||
if (_albumPreview) {
|
||||
_albumPreview->setSendWay(value);
|
||||
}
|
||||
setInnerFocus();
|
||||
});
|
||||
}
|
||||
|
||||
void SendFilesBox::refreshAlbumMediaCount() {
|
||||
_albumVideosCount = _list.albumIsPossible
|
||||
? ranges::count(
|
||||
_list.files,
|
||||
Storage::PreparedFile::AlbumType::Video,
|
||||
[](const Storage::PreparedFile &file) { return file.type; })
|
||||
: 0;
|
||||
_albumPhotosCount = _list.albumIsPossible
|
||||
? (_list.files.size() - _albumVideosCount)
|
||||
: 0;
|
||||
}
|
||||
|
||||
void SendFilesBox::preparePreview() {
|
||||
if (_list.files.size() == 1) {
|
||||
prepareSingleFilePreview();
|
||||
} else {
|
||||
if (_list.albumIsPossible) {
|
||||
prepareAlbumPreview();
|
||||
} else {
|
||||
auto desiredPreviewHeight = rpl::single(0);
|
||||
initPreview(std::move(desiredPreviewHeight));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SendFilesBox::setupControls() {
|
||||
@@ -1465,15 +1500,19 @@ void SendFilesBox::setupControls() {
|
||||
}
|
||||
|
||||
void SendFilesBox::setupSendWayControls() {
|
||||
_sendAlbum.destroy();
|
||||
_sendPhotos.destroy();
|
||||
_sendFiles.destroy();
|
||||
if (_compressConfirm == CompressConfirm::None) {
|
||||
return;
|
||||
}
|
||||
const auto addRadio = [&](
|
||||
object_ptr<Ui::Radioenum<SendFilesWay>> &button,
|
||||
SendFilesWay value,
|
||||
const QString &text) {
|
||||
object_ptr<Ui::Radioenum<SendFilesWay>> &button,
|
||||
SendFilesWay value,
|
||||
const QString &text) {
|
||||
const auto &style = st::defaultBoxCheckbox;
|
||||
button.create(this, _sendWay, value, text, style);
|
||||
button->show();
|
||||
};
|
||||
if (_list.albumIsPossible) {
|
||||
addRadio(_sendAlbum, SendFilesWay::Album, lang(lng_send_album));
|
||||
@@ -1490,17 +1529,12 @@ void SendFilesBox::setupSendWayControls() {
|
||||
addRadio(_sendFiles, SendFilesWay::Files, (_list.files.size() == 1)
|
||||
? lang(lng_send_file)
|
||||
: lng_send_files(lt_count, _list.files.size()));
|
||||
_sendWay->setChangedCallback([this](SendFilesWay value) {
|
||||
if (_albumPreview) {
|
||||
applyAlbumOrder();
|
||||
_albumPreview->setSendWay(value);
|
||||
}
|
||||
setInnerFocus();
|
||||
});
|
||||
}
|
||||
|
||||
void SendFilesBox::applyAlbumOrder() {
|
||||
Expects(_albumPreview != nullptr);
|
||||
if (!_albumPreview) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto order = _albumPreview->takeOrder();
|
||||
const auto isDefault = [&] {
|
||||
@@ -1519,10 +1553,12 @@ void SendFilesBox::applyAlbumOrder() {
|
||||
}
|
||||
|
||||
void SendFilesBox::setupCaption() {
|
||||
if (!_caption) {
|
||||
if (_caption) {
|
||||
_caption->setPlaceholder(FieldPlaceholder(_list));
|
||||
return;
|
||||
}
|
||||
|
||||
_caption.create(this, st::confirmCaptionArea, FieldPlaceholder(_list));
|
||||
_caption->setMaxLength(MaxPhotoCaption);
|
||||
_caption->setCtrlEnterSubmit(Ui::CtrlEnterSubmit::Both);
|
||||
connect(_caption, &Ui::InputArea::resized, this, [this] {
|
||||
@@ -1535,6 +1571,16 @@ void SendFilesBox::setupCaption() {
|
||||
connect(_caption, &Ui::InputArea::cancelled, this, [this] {
|
||||
closeBox();
|
||||
});
|
||||
_caption->setMimeDataHook([this](
|
||||
not_null<const QMimeData*> data,
|
||||
Ui::InputArea::MimeAction action) {
|
||||
if (action == Ui::InputArea::MimeAction::Check) {
|
||||
return canAddFiles(data);
|
||||
} else if (action == Ui::InputArea::MimeAction::Insert) {
|
||||
return addFiles(data);
|
||||
}
|
||||
Unexpected("action in MimeData hook.");
|
||||
});
|
||||
}
|
||||
|
||||
void SendFilesBox::captionResized() {
|
||||
@@ -1543,6 +1589,79 @@ void SendFilesBox::captionResized() {
|
||||
update();
|
||||
}
|
||||
|
||||
bool SendFilesBox::canAddFiles(not_null<const QMimeData*> data) const {
|
||||
auto files = 0;
|
||||
if (data->hasUrls()) {
|
||||
for (const auto &url : data->urls()) {
|
||||
if (url.isLocalFile()) {
|
||||
++files;
|
||||
}
|
||||
}
|
||||
} else if (data->hasImage()) {
|
||||
++files;
|
||||
}
|
||||
if (_list.files.size() + files > Storage::MaxAlbumItems()) {
|
||||
return false;
|
||||
} else if (_list.files.size() > 1 && !_albumPreview) {
|
||||
return false;
|
||||
} else if (_list.files.front().type
|
||||
== Storage::PreparedFile::AlbumType::None) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SendFilesBox::addFiles(not_null<const QMimeData*> data) {
|
||||
auto list = [&] {
|
||||
if (data->hasUrls()) {
|
||||
return Storage::PrepareMediaList(
|
||||
data->urls(),
|
||||
st::sendMediaPreviewSize);
|
||||
} else if (data->hasImage()) {
|
||||
auto image = qvariant_cast<QImage>(data->imageData());
|
||||
if (!image.isNull()) {
|
||||
return Storage::PrepareMediaFromImage(
|
||||
std::move(image),
|
||||
QByteArray(),
|
||||
st::sendMediaPreviewSize);
|
||||
}
|
||||
}
|
||||
return Storage::PreparedList(
|
||||
Storage::PreparedList::Error::EmptyFile,
|
||||
QString());
|
||||
}();
|
||||
if (_list.files.size() + list.files.size() > Storage::MaxAlbumItems()) {
|
||||
return false;
|
||||
} else if (list.error != Storage::PreparedList::Error::None) {
|
||||
return false;
|
||||
} else if (list.files.size() != 1 && !list.albumIsPossible) {
|
||||
return false;
|
||||
} else if (list.files.front().type
|
||||
== Storage::PreparedFile::AlbumType::None) {
|
||||
return false;
|
||||
} else if (_list.files.size() > 1 && !_albumPreview) {
|
||||
return false;
|
||||
} else if (_list.files.front().type
|
||||
== Storage::PreparedFile::AlbumType::None) {
|
||||
return false;
|
||||
}
|
||||
applyAlbumOrder();
|
||||
delete base::take(_preview);
|
||||
_albumPreview = nullptr;
|
||||
|
||||
if (_list.files.size() == 1
|
||||
&& _sendWay->value() == SendFilesWay::Photos) {
|
||||
_sendWay->setValue(SendFilesWay::Album);
|
||||
}
|
||||
_list.mergeToEnd(std::move(list));
|
||||
|
||||
_compressConfirm = _compressConfirmInitial;
|
||||
refreshAlbumMediaCount();
|
||||
preparePreview();
|
||||
updateControlsGeometry();
|
||||
return true;
|
||||
}
|
||||
|
||||
void SendFilesBox::setupTitleText() {
|
||||
if (_list.files.size() > 1) {
|
||||
const auto onlyImages = (_compressConfirm != CompressConfirm::None)
|
||||
@@ -1664,9 +1783,7 @@ void SendFilesBox::send(bool ctrlShiftEnter) {
|
||||
}
|
||||
}
|
||||
|
||||
if (_albumPreview) {
|
||||
applyAlbumOrder();
|
||||
}
|
||||
applyAlbumOrder();
|
||||
_confirmed = true;
|
||||
if (_confirmedCallback) {
|
||||
auto caption = _caption
|
||||
|
||||
@@ -83,6 +83,8 @@ private:
|
||||
not_null<Ui::ScrollArea*> wrap,
|
||||
not_null<AlbumPreview*> content);
|
||||
|
||||
void refreshAlbumMediaCount();
|
||||
void preparePreview();
|
||||
void prepareSingleFilePreview();
|
||||
void prepareAlbumPreview();
|
||||
void applyAlbumOrder();
|
||||
@@ -94,11 +96,15 @@ private:
|
||||
void updateBoxSize();
|
||||
void updateControlsGeometry();
|
||||
|
||||
bool canAddFiles(not_null<const QMimeData*> data) const;
|
||||
bool addFiles(not_null<const QMimeData*> data);
|
||||
|
||||
QString _titleText;
|
||||
int _titleHeight = 0;
|
||||
|
||||
Storage::PreparedList _list;
|
||||
|
||||
CompressConfirm _compressConfirmInitial = CompressConfirm::None;
|
||||
CompressConfirm _compressConfirm = CompressConfirm::None;
|
||||
|
||||
base::lambda<void(
|
||||
|
||||
@@ -33,10 +33,11 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "apiwrap.h"
|
||||
#include "ui/toast/toast.h"
|
||||
#include "ui/widgets/multi_select.h"
|
||||
#include "history/history_media_types.h"
|
||||
#include "history/history_message.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/scroll_area.h"
|
||||
#include "ui/text_options.h"
|
||||
#include "history/history_media_types.h"
|
||||
#include "history/history_message.h"
|
||||
#include "window/themes/window_theme.h"
|
||||
#include "boxes/peer_list_box.h"
|
||||
#include "auth_session.h"
|
||||
@@ -377,7 +378,7 @@ void ShareBox::Inner::updateChatName(
|
||||
not_null<Chat*> chat,
|
||||
not_null<PeerData*> peer) {
|
||||
const auto text = peer->isSelf() ? lang(lng_saved_messages) : peer->name;
|
||||
chat->name.setText(st::shareNameStyle, text, _textNameOptions);
|
||||
chat->name.setText(st::shareNameStyle, text, Ui::NameTextOptions());
|
||||
}
|
||||
|
||||
void ShareBox::Inner::repaintChatAtIndex(int index) {
|
||||
|
||||
@@ -37,7 +37,6 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "apiwrap.h"
|
||||
#include "observer_peer.h"
|
||||
#include "platform/platform_specific.h"
|
||||
#include "base/task_queue.h"
|
||||
#include "window/main_window.h"
|
||||
|
||||
namespace Calls {
|
||||
@@ -425,10 +424,8 @@ void Panel::showControls() {
|
||||
|
||||
void Panel::destroyDelayed() {
|
||||
hide();
|
||||
base::TaskQueue::Main().Put([weak = QPointer<Panel>(this)] {
|
||||
if (weak) {
|
||||
delete weak.data();
|
||||
}
|
||||
crl::on_main(this, [=] {
|
||||
delete this;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
197
Telegram/SourceFiles/core/changelogs.cpp
Normal file
197
Telegram/SourceFiles/core/changelogs.cpp
Normal file
@@ -0,0 +1,197 @@
|
||||
/*
|
||||
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-2017 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#include "core/changelogs.h"
|
||||
|
||||
#include "storage/localstorage.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "mainwindow.h"
|
||||
#include "apiwrap.h"
|
||||
|
||||
namespace Core {
|
||||
namespace {
|
||||
|
||||
std::map<int, const char*> AlphaLogs() {
|
||||
return {
|
||||
{
|
||||
1001024,
|
||||
"\xE2\x80\x94 Radically improved navigation. "
|
||||
"New side panel on the right with quick access to "
|
||||
"shared media and group members.\n"
|
||||
|
||||
"\xE2\x80\x94 Pinned Messages. If you are a channel admin, "
|
||||
"pin messages to focus your subscribers\xE2\x80\x99 attention "
|
||||
"on important announcements.\n"
|
||||
|
||||
"\xE2\x80\x94 Also supported clearing history in supergroups "
|
||||
"and added a host of minor improvements."
|
||||
},
|
||||
{
|
||||
1001026,
|
||||
"\xE2\x80\x94 Admin badges in supergroup messages.\n"
|
||||
"\xE2\x80\x94 Fix crashing on launch in OS X 10.6.\n"
|
||||
"\xE2\x80\x94 Bug fixes and other minor improvements."
|
||||
},
|
||||
{
|
||||
1001027,
|
||||
"\xE2\x80\x94 Saved Messages. Bookmark messages by forwarding them "
|
||||
"to \xE2\x80\x9C""Saved Messages\xE2\x80\x9D. "
|
||||
"Access them from the Chats list or from the side menu."
|
||||
},
|
||||
{
|
||||
1002002,
|
||||
"\xE2\x80\x94 Grouped photos and videos are displayed as albums."
|
||||
},
|
||||
{
|
||||
1002004,
|
||||
"\xE2\x80\x94 Group media into an album "
|
||||
"when sharing multiple photos and videos.\n"
|
||||
|
||||
"\xE2\x80\x94 Bug fixes and other minor improvements."
|
||||
},
|
||||
{
|
||||
1002005,
|
||||
"\xE2\x80\x94 When viewing a photo from an album, "
|
||||
"you'll see other pictures from the same group "
|
||||
"as thumbnails in the lower part of the screen.\n"
|
||||
|
||||
"\xE2\x80\x94 When composing an album paste "
|
||||
"additional media from the clipboard.\n"
|
||||
|
||||
"\xE2\x80\x94 Bug fixes and other minor improvements."
|
||||
},
|
||||
{
|
||||
1002007,
|
||||
"\xE2\x80\x94 Use fast reply button in group chats.\n"
|
||||
|
||||
"\xE2\x80\x94 Select a message you want to reply to by "
|
||||
"pressing Ctrl+Up and Ctrl+Down."
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
QString FormatVersionDisplay(int version) {
|
||||
return QString::number(version / 1000000)
|
||||
+ '.' + QString::number((version % 1000000) / 1000)
|
||||
+ ((version % 1000)
|
||||
? ('.' + QString::number(version % 1000))
|
||||
: QString());
|
||||
}
|
||||
|
||||
QString FormatVersionPrecise(int version) {
|
||||
return QString::number(version / 1000000)
|
||||
+ '.' + QString::number((version % 1000000) / 1000)
|
||||
+ '.' + QString::number(version % 1000);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Changelogs::Changelogs(not_null<AuthSession*> session, int oldVersion)
|
||||
: _session(session)
|
||||
, _oldVersion(oldVersion) {
|
||||
_chatsSubscription = subscribe(
|
||||
_session->data().moreChatsLoaded(),
|
||||
[this] { requestCloudLogs(); });
|
||||
}
|
||||
|
||||
std::unique_ptr<Changelogs> Changelogs::Create(
|
||||
not_null<AuthSession*> session) {
|
||||
const auto oldVersion = Local::oldMapVersion();
|
||||
return (oldVersion > 0 && oldVersion < AppVersion)
|
||||
? std::make_unique<Changelogs>(session, oldVersion)
|
||||
: nullptr;
|
||||
}
|
||||
|
||||
void Changelogs::requestCloudLogs() {
|
||||
unsubscribe(base::take(_chatsSubscription));
|
||||
|
||||
const auto callback = [this](const MTPUpdates &result) {
|
||||
_session->api().applyUpdates(result);
|
||||
|
||||
auto resultEmpty = true;
|
||||
switch (result.type()) {
|
||||
case mtpc_updateShortMessage:
|
||||
case mtpc_updateShortChatMessage:
|
||||
case mtpc_updateShort:
|
||||
resultEmpty = false;
|
||||
break;
|
||||
case mtpc_updatesCombined:
|
||||
resultEmpty = result.c_updatesCombined().vupdates.v.isEmpty();
|
||||
break;
|
||||
case mtpc_updates:
|
||||
resultEmpty = result.c_updates().vupdates.v.isEmpty();
|
||||
break;
|
||||
case mtpc_updatesTooLong:
|
||||
case mtpc_updateShortSentMessage:
|
||||
LOG(("API Error: Bad updates type in app changelog."));
|
||||
break;
|
||||
}
|
||||
if (resultEmpty) {
|
||||
addLocalLogs();
|
||||
}
|
||||
};
|
||||
_session->api().requestChangelog(
|
||||
FormatVersionPrecise(_oldVersion),
|
||||
base::lambda_guarded(this, callback));
|
||||
}
|
||||
|
||||
void Changelogs::addLocalLogs() {
|
||||
if (cAlphaVersion() || cBetaVersion()) {
|
||||
addAlphaLogs();
|
||||
}
|
||||
if (!_addedSomeLocal) {
|
||||
const auto text = lng_new_version_wrap(
|
||||
lt_version,
|
||||
str_const_toString(AppVersionStr),
|
||||
lt_changes,
|
||||
lang(lng_new_version_minor),
|
||||
lt_link,
|
||||
qsl("https://desktop.telegram.org/changelog"));
|
||||
addLocalLog(text.trimmed());
|
||||
}
|
||||
}
|
||||
|
||||
void Changelogs::addLocalLog(const QString &text) {
|
||||
auto textWithEntities = TextWithEntities{ text };
|
||||
TextUtilities::ParseEntities(textWithEntities, TextParseLinks);
|
||||
App::wnd()->serviceNotification(
|
||||
textWithEntities,
|
||||
MTP_messageMediaEmpty(),
|
||||
unixtime());
|
||||
_addedSomeLocal = true;
|
||||
};
|
||||
|
||||
void Changelogs::addAlphaLogs() {
|
||||
for (const auto[version, changes] : AlphaLogs()) {
|
||||
addAlphaLog(version, changes);
|
||||
}
|
||||
}
|
||||
|
||||
void Changelogs::addAlphaLog(int changeVersion, const char *changes) {
|
||||
if (_oldVersion >= changeVersion) {
|
||||
return;
|
||||
}
|
||||
const auto version = FormatVersionDisplay(changeVersion);
|
||||
const auto text = qsl("New in version %1:\n\n").arg(version)
|
||||
+ QString::fromUtf8(changes).trimmed();
|
||||
addLocalLog(text);
|
||||
}
|
||||
|
||||
} // namespace Core
|
||||
50
Telegram/SourceFiles/core/changelogs.h
Normal file
50
Telegram/SourceFiles/core/changelogs.h
Normal file
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
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-2017 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "base/weak_ptr.h"
|
||||
|
||||
class AuthSession;
|
||||
|
||||
namespace Core {
|
||||
|
||||
class Changelogs : public base::has_weak_ptr, private base::Subscriber {
|
||||
public:
|
||||
Changelogs(not_null<AuthSession*> session, int oldVersion);
|
||||
|
||||
static std::unique_ptr<Changelogs> Create(
|
||||
not_null<AuthSession*> session);
|
||||
|
||||
private:
|
||||
void requestCloudLogs();
|
||||
void addLocalLogs();
|
||||
void addLocalLog(const QString &text);
|
||||
void addAlphaLogs();
|
||||
void addAlphaLog(int changeVersion, const char *changes);
|
||||
|
||||
const not_null<AuthSession*> _session;
|
||||
const int _oldVersion = 0;
|
||||
int _chatsSubscription = 0;
|
||||
bool _addedSomeLocal = false;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Core
|
||||
@@ -23,7 +23,6 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "mainwindow.h"
|
||||
#include "storage/localstorage.h"
|
||||
#include "platform/platform_file_utilities.h"
|
||||
#include "base/task_queue.h"
|
||||
#include "messenger.h"
|
||||
|
||||
bool filedialogGetSaveFile(
|
||||
@@ -99,13 +98,13 @@ QString filedialogNextFilename(
|
||||
namespace File {
|
||||
|
||||
void OpenEmailLink(const QString &email) {
|
||||
base::TaskQueue::Main().Put([email] {
|
||||
crl::on_main([=] {
|
||||
Platform::File::UnsafeOpenEmailLink(email);
|
||||
});
|
||||
}
|
||||
|
||||
void OpenWith(const QString &filepath, QPoint menuPosition) {
|
||||
base::TaskQueue::Main().Put([filepath, menuPosition] {
|
||||
crl::on_main([=] {
|
||||
if (!Platform::File::UnsafeShowOpenWithDropdown(filepath, menuPosition)) {
|
||||
if (!Platform::File::UnsafeShowOpenWith(filepath)) {
|
||||
Platform::File::UnsafeLaunch(filepath);
|
||||
@@ -115,13 +114,13 @@ void OpenWith(const QString &filepath, QPoint menuPosition) {
|
||||
}
|
||||
|
||||
void Launch(const QString &filepath) {
|
||||
base::TaskQueue::Main().Put([filepath] {
|
||||
crl::on_main([=] {
|
||||
Platform::File::UnsafeLaunch(filepath);
|
||||
});
|
||||
}
|
||||
|
||||
void ShowInFolder(const QString &filepath) {
|
||||
base::TaskQueue::Main().Put([filepath] {
|
||||
crl::on_main([=] {
|
||||
Platform::File::UnsafeShowInFolder(filepath);
|
||||
});
|
||||
}
|
||||
@@ -147,7 +146,7 @@ void GetOpenPath(
|
||||
const QString &filter,
|
||||
base::lambda<void(OpenResult &&result)> callback,
|
||||
base::lambda<void()> failed) {
|
||||
base::TaskQueue::Main().Put([=] {
|
||||
crl::on_main([=] {
|
||||
auto files = QStringList();
|
||||
auto remoteContent = QByteArray();
|
||||
const auto success = Platform::FileDialog::Get(
|
||||
@@ -178,7 +177,7 @@ void GetOpenPaths(
|
||||
const QString &filter,
|
||||
base::lambda<void(OpenResult &&result)> callback,
|
||||
base::lambda<void()> failed) {
|
||||
base::TaskQueue::Main().Put([=] {
|
||||
crl::on_main([=] {
|
||||
auto files = QStringList();
|
||||
auto remoteContent = QByteArray();
|
||||
const auto success = Platform::FileDialog::Get(
|
||||
@@ -206,7 +205,7 @@ void GetWritePath(
|
||||
const QString &initialPath,
|
||||
base::lambda<void(QString &&result)> callback,
|
||||
base::lambda<void()> failed) {
|
||||
base::TaskQueue::Main().Put([=] {
|
||||
crl::on_main([=] {
|
||||
auto file = QString();
|
||||
if (filedialogGetSaveFile(file, caption, filter, initialPath)) {
|
||||
if (callback) {
|
||||
@@ -223,7 +222,7 @@ void GetFolder(
|
||||
const QString &initialPath,
|
||||
base::lambda<void(QString &&result)> callback,
|
||||
base::lambda<void()> failed) {
|
||||
base::TaskQueue::Main().Put([=] {
|
||||
crl::on_main([=] {
|
||||
auto files = QStringList();
|
||||
auto remoteContent = QByteArray();
|
||||
const auto success = Platform::FileDialog::Get(
|
||||
|
||||
@@ -24,7 +24,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
|
||||
#define BETA_VERSION_MACRO (0ULL)
|
||||
|
||||
constexpr int AppVersion = 1002004;
|
||||
constexpr str_const AppVersionStr = "1.2.4";
|
||||
constexpr int AppVersion = 1002007;
|
||||
constexpr str_const AppVersionStr = "1.2.7";
|
||||
constexpr bool AppAlphaVersion = true;
|
||||
constexpr uint64 AppBetaVersion = BETA_VERSION_MACRO;
|
||||
|
||||
@@ -36,6 +36,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "mainwindow.h"
|
||||
#include "window/window_controller.h"
|
||||
#include "ui/empty_userpic.h"
|
||||
#include "ui/text_options.h"
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -100,7 +101,7 @@ void PeerClickHandler::onClick(Qt::MouseButton button) const {
|
||||
PeerData::PeerData(const PeerId &id)
|
||||
: id(id)
|
||||
, _userpicEmpty(createEmptyUserpic()) {
|
||||
nameText.setText(st::msgNameStyle, QString(), _textNameOptions);
|
||||
nameText.setText(st::msgNameStyle, QString(), Ui::NameTextOptions());
|
||||
}
|
||||
|
||||
void PeerData::updateNameDelayed(
|
||||
@@ -124,7 +125,7 @@ void PeerData::updateNameDelayed(
|
||||
|
||||
++nameVersion;
|
||||
name = newName;
|
||||
nameText.setText(st::msgNameStyle, name, _textNameOptions);
|
||||
nameText.setText(st::msgNameStyle, name, Ui::NameTextOptions());
|
||||
refreshEmptyUserpic();
|
||||
|
||||
Notify::PeerUpdate update(this);
|
||||
@@ -351,7 +352,10 @@ PeerData::~PeerData() = default;
|
||||
|
||||
const Text &BotCommand::descriptionText() const {
|
||||
if (_descriptionText.isEmpty() && !_description.isEmpty()) {
|
||||
_descriptionText.setText(st::defaultTextStyle, _description, _textNameOptions);
|
||||
_descriptionText.setText(
|
||||
st::defaultTextStyle,
|
||||
_description,
|
||||
Ui::NameTextOptions());
|
||||
}
|
||||
return _descriptionText;
|
||||
}
|
||||
@@ -491,7 +495,10 @@ void UserData::setBotInfo(const MTPBotInfo &info) {
|
||||
void UserData::setNameOrPhone(const QString &newNameOrPhone) {
|
||||
if (nameOrPhone != newNameOrPhone) {
|
||||
nameOrPhone = newNameOrPhone;
|
||||
phoneText.setText(st::msgNameStyle, nameOrPhone, _textNameOptions);
|
||||
phoneText.setText(
|
||||
st::msgNameStyle,
|
||||
nameOrPhone,
|
||||
Ui::NameTextOptions());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -36,13 +36,15 @@ MTPmessages_Search PrepareSearchRequest(
|
||||
const QString &query,
|
||||
MsgId messageId,
|
||||
SparseIdsLoadDirection direction) {
|
||||
auto filter = [&] {
|
||||
const auto filter = [&] {
|
||||
using Type = Storage::SharedMediaType;
|
||||
switch (type) {
|
||||
case Type::Photo:
|
||||
return MTP_inputMessagesFilterPhotos();
|
||||
case Type::Video:
|
||||
return MTP_inputMessagesFilterVideo();
|
||||
case Type::PhotoVideo:
|
||||
return MTP_inputMessagesFilterPhotoVideo();
|
||||
case Type::MusicFile:
|
||||
return MTP_inputMessagesFilterMusic();
|
||||
case Type::File:
|
||||
@@ -63,10 +65,10 @@ MTPmessages_Search PrepareSearchRequest(
|
||||
return MTP_inputMessagesFilterEmpty();
|
||||
}();
|
||||
|
||||
auto minId = 0;
|
||||
auto maxId = 0;
|
||||
auto limit = messageId ? kSharedMediaLimit : 0;
|
||||
auto offsetId = [&] {
|
||||
const auto minId = 0;
|
||||
const auto maxId = 0;
|
||||
const auto limit = messageId ? kSharedMediaLimit : 0;
|
||||
const auto offsetId = [&] {
|
||||
switch (direction) {
|
||||
case SparseIdsLoadDirection::Before:
|
||||
case SparseIdsLoadDirection::Around: return messageId;
|
||||
@@ -74,7 +76,7 @@ MTPmessages_Search PrepareSearchRequest(
|
||||
}
|
||||
Unexpected("Direction in PrepareSearchRequest");
|
||||
}();
|
||||
auto addOffset = [&] {
|
||||
const auto addOffset = [&] {
|
||||
switch (direction) {
|
||||
case SparseIdsLoadDirection::Before: return 0;
|
||||
case SparseIdsLoadDirection::Around: return -limit / 2;
|
||||
@@ -130,7 +132,8 @@ SearchResult ParseSearchResult(
|
||||
if (auto channel = peer->asChannel()) {
|
||||
channel->ptsReceived(d.vpts.v);
|
||||
} else {
|
||||
LOG(("API Error: received messages.channelMessages when no channel was passed! (ParseSearchResult)"));
|
||||
LOG(("API Error: received messages.channelMessages when "
|
||||
"no channel was passed! (ParseSearchResult)"));
|
||||
}
|
||||
App::feedUsers(d.vusers);
|
||||
App::feedChats(d.vchats);
|
||||
@@ -139,7 +142,8 @@ SearchResult ParseSearchResult(
|
||||
} break;
|
||||
|
||||
case mtpc_messages_messagesNotModified: {
|
||||
LOG(("API Error: received messages.messagesNotModified! (ParseSearchResult)"));
|
||||
LOG(("API Error: received messages.messagesNotModified! "
|
||||
"(ParseSearchResult)"));
|
||||
return (const QVector<MTPMessage>*)nullptr;
|
||||
} break;
|
||||
}
|
||||
|
||||
122
Telegram/SourceFiles/data/data_web_page.cpp
Normal file
122
Telegram/SourceFiles/data/data_web_page.cpp
Normal file
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
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-2017 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#include "data/data_web_page.h"
|
||||
|
||||
#include "auth_session.h"
|
||||
#include "apiwrap.h"
|
||||
#include "mainwidget.h"
|
||||
#include "ui/text/text_entity.h"
|
||||
|
||||
namespace {
|
||||
|
||||
QString SiteNameFromUrl(const QString &url) {
|
||||
QUrl u(url);
|
||||
QString pretty = u.isValid() ? u.toDisplayString() : url;
|
||||
QRegularExpressionMatch m = QRegularExpression(qsl("^[a-zA-Z0-9]+://")).match(pretty);
|
||||
if (m.hasMatch()) pretty = pretty.mid(m.capturedLength());
|
||||
int32 slash = pretty.indexOf('/');
|
||||
if (slash > 0) pretty = pretty.mid(0, slash);
|
||||
QStringList components = pretty.split('.', QString::SkipEmptyParts);
|
||||
if (components.size() >= 2) {
|
||||
components = components.mid(components.size() - 2);
|
||||
return components.at(0).at(0).toUpper() + components.at(0).mid(1) + '.' + components.at(1);
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool WebPageData::applyChanges(
|
||||
const QString &newType,
|
||||
const QString &newUrl,
|
||||
const QString &newDisplayUrl,
|
||||
const QString &newSiteName,
|
||||
const QString &newTitle,
|
||||
const TextWithEntities &newDescription,
|
||||
PhotoData *newPhoto,
|
||||
DocumentData *newDocument,
|
||||
int newDuration,
|
||||
const QString &newAuthor,
|
||||
int newPendingTill) {
|
||||
if (newPendingTill != 0
|
||||
&& (!url.isEmpty() || newUrl.isEmpty())
|
||||
&& (!pendingTill
|
||||
|| pendingTill == newPendingTill
|
||||
|| newPendingTill < -1)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto resultType = toWebPageType(newType);
|
||||
const auto resultUrl = TextUtilities::Clean(newUrl);
|
||||
const auto resultDisplayUrl = TextUtilities::Clean(
|
||||
newDisplayUrl);
|
||||
const auto possibleSiteName = TextUtilities::Clean(
|
||||
newSiteName);
|
||||
const auto resultTitle = TextUtilities::SingleLine(
|
||||
newTitle);
|
||||
const auto resultAuthor = TextUtilities::Clean(newAuthor);
|
||||
|
||||
const auto viewTitleText = resultTitle.isEmpty()
|
||||
? TextUtilities::SingleLine(resultAuthor)
|
||||
: resultTitle;
|
||||
const auto resultSiteName = [&] {
|
||||
if (!possibleSiteName.isEmpty()) {
|
||||
return possibleSiteName;
|
||||
} else if (!newDescription.text.isEmpty()
|
||||
&& viewTitleText.isEmpty()
|
||||
&& !resultUrl.isEmpty()) {
|
||||
return SiteNameFromUrl(resultUrl);
|
||||
}
|
||||
return QString();
|
||||
}();
|
||||
|
||||
if (type == resultType
|
||||
&& url == resultUrl
|
||||
&& displayUrl == resultDisplayUrl
|
||||
&& siteName == resultSiteName
|
||||
&& title == resultTitle
|
||||
&& description.text == newDescription.text
|
||||
&& photo == newPhoto
|
||||
&& document == newDocument
|
||||
&& duration == newDuration
|
||||
&& author == resultAuthor
|
||||
&& pendingTill == newPendingTill) {
|
||||
return false;
|
||||
}
|
||||
if (pendingTill > 0 && newPendingTill <= 0) {
|
||||
Auth().api().clearWebPageRequest(this);
|
||||
}
|
||||
type = resultType;
|
||||
url = resultUrl;
|
||||
displayUrl = resultDisplayUrl;
|
||||
siteName = resultSiteName;
|
||||
title = resultTitle;
|
||||
description = newDescription;
|
||||
photo = newPhoto;
|
||||
document = newDocument;
|
||||
duration = newDuration;
|
||||
author = resultAuthor;
|
||||
pendingTill = newPendingTill;
|
||||
++version;
|
||||
if (App::main()) App::main()->webPageUpdated(this);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -50,9 +50,9 @@ struct WebPageData {
|
||||
const TextWithEntities &description,
|
||||
DocumentData *document,
|
||||
PhotoData *photo,
|
||||
int32 duration,
|
||||
int duration,
|
||||
const QString &author,
|
||||
int32 pendingTill)
|
||||
int pendingTill)
|
||||
: id(id)
|
||||
, type(type)
|
||||
, url(url)
|
||||
@@ -72,6 +72,19 @@ struct WebPageData {
|
||||
if (photo) photo->forget();
|
||||
}
|
||||
|
||||
bool applyChanges(
|
||||
const QString &newType,
|
||||
const QString &newUrl,
|
||||
const QString &newDisplayUrl,
|
||||
const QString &newSiteName,
|
||||
const QString &newTitle,
|
||||
const TextWithEntities &newDescription,
|
||||
PhotoData *newPhoto,
|
||||
DocumentData *newDocument,
|
||||
int newDuration,
|
||||
const QString &newAuthor,
|
||||
int newPendingTill);
|
||||
|
||||
WebPageId id = 0;
|
||||
WebPageType type = WebPageArticle;
|
||||
QString url;
|
||||
@@ -79,10 +92,11 @@ struct WebPageData {
|
||||
QString siteName;
|
||||
QString title;
|
||||
TextWithEntities description;
|
||||
int32 duration = 0;
|
||||
int duration = 0;
|
||||
QString author;
|
||||
PhotoData *photo = nullptr;
|
||||
DocumentData *document = nullptr;
|
||||
int32 pendingTill = 0;
|
||||
int pendingTill = 0;
|
||||
int version = 0;
|
||||
|
||||
};
|
||||
|
||||
@@ -28,6 +28,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "styles/style_window.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/widgets/popup_menu.h"
|
||||
#include "ui/text_options.h"
|
||||
#include "data/data_drafts.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "mainwindow.h"
|
||||
@@ -1799,7 +1800,7 @@ void DialogsInner::searchInPeer(PeerData *peer, UserData *from) {
|
||||
_searchInSavedText.setText(
|
||||
st::msgNameStyle,
|
||||
lang(lng_saved_messages),
|
||||
_textDlgOptions);
|
||||
Ui::DialogTextOptions());
|
||||
}
|
||||
} else {
|
||||
_cancelSearchInPeer->hide();
|
||||
@@ -1811,7 +1812,7 @@ void DialogsInner::searchInPeer(PeerData *peer, UserData *from) {
|
||||
_searchFromUserText.setText(
|
||||
st::dialogsSearchFromStyle,
|
||||
fromUserText,
|
||||
_textDlgOptions);
|
||||
Ui::DialogTextOptions());
|
||||
_cancelSearchFromUser->show();
|
||||
} else {
|
||||
_cancelSearchFromUser->hide();
|
||||
|
||||
@@ -26,6 +26,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "styles/style_dialogs.h"
|
||||
#include "storage/localstorage.h"
|
||||
#include "ui/empty_userpic.h"
|
||||
#include "ui/text_options.h"
|
||||
#include "lang/lang_keys.h"
|
||||
|
||||
namespace Dialogs {
|
||||
@@ -156,7 +157,7 @@ void paintRow(
|
||||
if (history->cloudDraftTextCache.isEmpty()) {
|
||||
auto draftWrapped = textcmdLink(1, lng_dialogs_text_from_wrapped(lt_from, lang(lng_from_draft)));
|
||||
auto draftText = lng_dialogs_text_with_from(lt_from_part, draftWrapped, lt_message, TextUtilities::Clean(draft->textWithTags.text));
|
||||
history->cloudDraftTextCache.setText(st::dialogsTextStyle, draftText, _textDlgOptions);
|
||||
history->cloudDraftTextCache.setText(st::dialogsTextStyle, draftText, Ui::DialogTextOptions());
|
||||
}
|
||||
p.setPen(active ? st::dialogsTextFgActive : (selected ? st::dialogsTextFgOver : st::dialogsTextFg));
|
||||
p.setTextPalette(active ? st::dialogsTextPaletteDraftActive : (selected ? st::dialogsTextPaletteDraftOver : st::dialogsTextPaletteDraft));
|
||||
|
||||
@@ -35,7 +35,6 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "window/layer_widget.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "base/observer.h"
|
||||
#include "base/task_queue.h"
|
||||
#include "history/history_media.h"
|
||||
#include "styles/style_history.h"
|
||||
|
||||
@@ -508,20 +507,7 @@ void WorkingDirReady() {
|
||||
}
|
||||
}
|
||||
|
||||
object_ptr<SingleQueuedInvokation> MainThreadTaskHandler = { nullptr };
|
||||
|
||||
void MainThreadTaskAdded() {
|
||||
if (!started()) {
|
||||
return;
|
||||
}
|
||||
|
||||
MainThreadTaskHandler->call();
|
||||
}
|
||||
|
||||
void start() {
|
||||
MainThreadTaskHandler.create([] {
|
||||
base::TaskQueue::ProcessMainTasks();
|
||||
});
|
||||
SandboxData = std::make_unique<internal::Data>();
|
||||
}
|
||||
|
||||
@@ -531,7 +517,6 @@ bool started() {
|
||||
|
||||
void finish() {
|
||||
SandboxData.reset();
|
||||
MainThreadTaskHandler.destroy();
|
||||
}
|
||||
|
||||
uint64 UserTag() {
|
||||
|
||||
@@ -39,6 +39,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "storage/storage_facade.h"
|
||||
#include "storage/storage_shared_media.h"
|
||||
#include "data/data_channel_admins.h"
|
||||
#include "ui/text_options.h"
|
||||
#include "core/crash_reports.h"
|
||||
|
||||
namespace {
|
||||
@@ -62,7 +63,7 @@ auto GlobalPinnedIndex = 0;
|
||||
|
||||
HistoryItem *createUnsupportedMessage(History *history, MsgId msgId, MTPDmessage::Flags flags, MsgId replyTo, int32 viaBotId, QDateTime date, int32 from) {
|
||||
auto text = TextWithEntities { lng_message_unsupported(lt_link, qsl("https://desktop.telegram.org")) };
|
||||
TextUtilities::ParseEntities(text, _historyTextNoMonoOptions.flags);
|
||||
TextUtilities::ParseEntities(text, Ui::ItemTextNoMonoOptions().flags);
|
||||
text.entities.push_front(EntityInText(EntityInTextItalic, 0, text.text.size()));
|
||||
flags &= ~MTPDmessage::Flag::f_post_author;
|
||||
return HistoryMessage::create(history, msgId, flags, replyTo, viaBotId, date, from, QString(), text);
|
||||
@@ -70,11 +71,6 @@ HistoryItem *createUnsupportedMessage(History *history, MsgId msgId, MTPDmessage
|
||||
|
||||
} // namespace
|
||||
|
||||
void HistoryInit() {
|
||||
HistoryInitMessages();
|
||||
HistoryInitMedia();
|
||||
}
|
||||
|
||||
History::History(const PeerId &peerId)
|
||||
: peer(App::peer(peerId))
|
||||
, lastItemTextCache(st::dialogsTextWidthMin)
|
||||
@@ -364,7 +360,10 @@ bool History::updateSendActionNeedsAnimating(TimeMs ms, bool force) {
|
||||
}
|
||||
if (_sendActionString != newTypingString) {
|
||||
_sendActionString = newTypingString;
|
||||
_sendActionText.setText(st::dialogsTextStyle, _sendActionString, _textNameOptions);
|
||||
_sendActionText.setText(
|
||||
st::dialogsTextStyle,
|
||||
_sendActionString,
|
||||
Ui::NameTextOptions());
|
||||
}
|
||||
}
|
||||
auto result = (!_typing.isEmpty() || !_sendActions.isEmpty());
|
||||
|
||||
@@ -30,8 +30,6 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "base/flat_set.h"
|
||||
#include "base/flags.h"
|
||||
|
||||
void HistoryInit();
|
||||
|
||||
class HistoryItem;
|
||||
using HistoryItemsList = std::vector<not_null<HistoryItem*>>;
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "history/history_service_layout.h"
|
||||
#include "history/history_media_types.h"
|
||||
#include "history/history_item_components.h"
|
||||
#include "ui/text_options.h"
|
||||
#include "ui/widgets/popup_menu.h"
|
||||
#include "window/window_controller.h"
|
||||
#include "window/window_peer_menu.h"
|
||||
@@ -1836,7 +1837,10 @@ void HistoryInner::updateBotInfo(bool recount) {
|
||||
int newh = 0;
|
||||
if (_botAbout && !_botAbout->info->description.isEmpty()) {
|
||||
if (_botAbout->info->text.isEmpty()) {
|
||||
_botAbout->info->text.setText(st::messageTextStyle, _botAbout->info->description, _historyBotNoMonoOptions);
|
||||
_botAbout->info->text.setText(
|
||||
st::messageTextStyle,
|
||||
_botAbout->info->description,
|
||||
Ui::ItemTextBotNoMonoOptions());
|
||||
if (recount) {
|
||||
int32 tw = _scroll->width() - st::msgMargin.left() - st::msgMargin.right();
|
||||
if (tw > st::msgMaxWidth) tw = st::msgMaxWidth;
|
||||
|
||||
@@ -31,6 +31,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "styles/style_dialogs.h"
|
||||
#include "styles/style_history.h"
|
||||
#include "ui/effects/ripple_animation.h"
|
||||
#include "ui/text_options.h"
|
||||
#include "storage/file_upload.h"
|
||||
#include "storage/storage_facade.h"
|
||||
#include "storage/storage_shared_media.h"
|
||||
@@ -1032,7 +1033,7 @@ void HistoryItem::drawInDialog(
|
||||
Text &cache) const {
|
||||
if (cacheFor != this) {
|
||||
cacheFor = this;
|
||||
cache.setText(st::dialogsTextStyle, inDialogsText(way), _textDlgOptions);
|
||||
cache.setText(st::dialogsTextStyle, inDialogsText(way), Ui::DialogTextOptions());
|
||||
}
|
||||
if (r.width()) {
|
||||
p.setTextPalette(active ? st::dialogsTextPaletteActive : (selected ? st::dialogsTextPaletteOver : st::dialogsTextPalette));
|
||||
|
||||
@@ -577,6 +577,31 @@ public:
|
||||
setAttachToNext(attachToNext);
|
||||
}
|
||||
|
||||
HistoryItem *previousItem() const {
|
||||
if (_block && _indexInBlock >= 0) {
|
||||
if (_indexInBlock > 0) {
|
||||
return _block->items.at(_indexInBlock - 1);
|
||||
}
|
||||
if (auto previous = _block->previousBlock()) {
|
||||
Assert(!previous->items.empty());
|
||||
return previous->items.back();
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
HistoryItem *nextItem() const {
|
||||
if (_block && _indexInBlock >= 0) {
|
||||
if (_indexInBlock + 1 < _block->items.size()) {
|
||||
return _block->items.at(_indexInBlock + 1);
|
||||
}
|
||||
if (auto next = _block->nextBlock()) {
|
||||
Assert(!next->items.empty());
|
||||
return next->items.front();
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
~HistoryItem();
|
||||
|
||||
protected:
|
||||
@@ -608,31 +633,6 @@ protected:
|
||||
int _indexInBlock = -1;
|
||||
MTPDmessage::Flags _flags = 0;
|
||||
|
||||
HistoryItem *previousItem() const {
|
||||
if (_block && _indexInBlock >= 0) {
|
||||
if (_indexInBlock > 0) {
|
||||
return _block->items.at(_indexInBlock - 1);
|
||||
}
|
||||
if (auto previous = _block->previousBlock()) {
|
||||
Assert(!previous->items.empty());
|
||||
return previous->items.back();
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
HistoryItem *nextItem() const {
|
||||
if (_block && _indexInBlock >= 0) {
|
||||
if (_indexInBlock + 1 < _block->items.size()) {
|
||||
return _block->items.at(_indexInBlock + 1);
|
||||
}
|
||||
if (auto next = _block->nextBlock()) {
|
||||
Assert(!next->items.empty());
|
||||
return next->items.front();
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// This should be called only from previousItemChanged()
|
||||
// to add required bits to the Composer mask
|
||||
// after that always use Has<HistoryMessageDate>().
|
||||
|
||||
@@ -22,6 +22,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
|
||||
#include "lang/lang_keys.h"
|
||||
#include "ui/effects/ripple_animation.h"
|
||||
#include "ui/text_options.h"
|
||||
#include "history/history_service_layout.h"
|
||||
#include "history/history_message.h"
|
||||
#include "history/history_media.h"
|
||||
@@ -63,7 +64,10 @@ void HistoryMessageSigned::refresh(const QString &date) {
|
||||
if (timew + namew > st::maxSignatureSize) {
|
||||
name = st::msgDateFont->elided(author, st::maxSignatureSize - timew);
|
||||
}
|
||||
signature.setText(st::msgDateTextStyle, name + time, _textNameOptions);
|
||||
signature.setText(
|
||||
st::msgDateTextStyle,
|
||||
name + time,
|
||||
Ui::NameTextOptions());
|
||||
}
|
||||
|
||||
int HistoryMessageSigned::maxWidth() const {
|
||||
@@ -72,7 +76,7 @@ int HistoryMessageSigned::maxWidth() const {
|
||||
|
||||
void HistoryMessageEdited::refresh(const QString &date, bool displayed) {
|
||||
const auto prefix = displayed ? (lang(lng_edited) + ' ') : QString();
|
||||
text.setText(st::msgDateTextStyle, prefix + date, _textNameOptions);
|
||||
text.setText(st::msgDateTextStyle, prefix + date, Ui::NameTextOptions());
|
||||
}
|
||||
|
||||
int HistoryMessageEdited::maxWidth() const {
|
||||
@@ -151,7 +155,10 @@ bool HistoryMessageReply::updateData(HistoryMessage *holder, bool force) {
|
||||
}
|
||||
|
||||
if (replyToMsg) {
|
||||
replyToText.setText(st::messageTextStyle, TextUtilities::Clean(replyToMsg->inReplyText()), _textDlgOptions);
|
||||
replyToText.setText(
|
||||
st::messageTextStyle,
|
||||
TextUtilities::Clean(replyToMsg->inReplyText()),
|
||||
Ui::DialogTextOptions());
|
||||
|
||||
updateName();
|
||||
|
||||
@@ -193,7 +200,7 @@ void HistoryMessageReply::updateName() const {
|
||||
QString name = (replyToVia && replyToMsg->author()->isUser())
|
||||
? replyToMsg->author()->asUser()->firstName
|
||||
: App::peerName(replyToMsg->author());
|
||||
replyToName.setText(st::fwdTextStyle, name, _textNameOptions);
|
||||
replyToName.setText(st::fwdTextStyle, name, Ui::NameTextOptions());
|
||||
replyToVersion = replyToMsg->author()->nameVersion;
|
||||
bool hasPreview = replyToMsg->getMedia() ? replyToMsg->getMedia()->hasReplyPreview() : false;
|
||||
int32 previewSkip = hasPreview ? (st::msgReplyBarSize.height() + st::msgReplyBarSkip - st::msgReplyBarSize.width() - st::msgReplyBarPos.x()) : 0;
|
||||
|
||||
@@ -26,6 +26,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "storage/storage_shared_media.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "ui/grouped_layout.h"
|
||||
#include "ui/text_options.h"
|
||||
#include "styles/style_history.h"
|
||||
|
||||
HistoryGroupedMedia::Element::Element(not_null<HistoryItem*> item)
|
||||
@@ -441,7 +442,7 @@ void HistoryGroupedMedia::updateNeedBubbleState() {
|
||||
_caption.setText(
|
||||
st::messageTextStyle,
|
||||
captionText.text + _parent->skipBlock(),
|
||||
itemTextNoMonoOptions(_parent));
|
||||
Ui::ItemTextNoMonoOptions(_parent));
|
||||
_needBubble = computeNeedBubble();
|
||||
}
|
||||
|
||||
|
||||
@@ -41,44 +41,13 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "calls/calls_instance.h"
|
||||
#include "ui/empty_userpic.h"
|
||||
#include "ui/grouped_layout.h"
|
||||
#include "ui/text_options.h"
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr auto kMaxGifForwardedBarLines = 4;
|
||||
constexpr auto kMaxOriginalEntryLines = 8192;
|
||||
|
||||
TextParseOptions _webpageTitleOptions = {
|
||||
TextParseMultiline | TextParseRichText, // flags
|
||||
0, // maxw
|
||||
0, // maxh
|
||||
Qt::LayoutDirectionAuto, // dir
|
||||
};
|
||||
TextParseOptions _webpageDescriptionOptions = {
|
||||
TextParseLinks | TextParseMentions | TextParseHashtags | TextParseMultiline | TextParseRichText | TextParseMarkdown, // flags
|
||||
0, // maxw
|
||||
0, // maxh
|
||||
Qt::LayoutDirectionAuto, // dir
|
||||
};
|
||||
TextParseOptions _twitterDescriptionOptions = {
|
||||
TextParseLinks | TextParseMentions | TextTwitterMentions | TextParseHashtags | TextTwitterHashtags | TextParseMultiline | TextParseRichText, // flags
|
||||
0, // maxw
|
||||
0, // maxh
|
||||
Qt::LayoutDirectionAuto, // dir
|
||||
};
|
||||
TextParseOptions _instagramDescriptionOptions = {
|
||||
TextParseLinks | TextParseMentions | TextInstagramMentions | TextParseHashtags | TextInstagramHashtags | TextParseMultiline | TextParseRichText, // flags
|
||||
0, // maxw
|
||||
0, // maxh
|
||||
Qt::LayoutDirectionAuto, // dir
|
||||
};
|
||||
|
||||
inline void initTextOptions() {
|
||||
_webpageTitleOptions.maxw = st::msgMaxWidth - st::msgPadding.left() - st::msgPadding.right() - st::webPageLeft;
|
||||
_webpageTitleOptions.maxh = st::webPageTitleFont->height * 2;
|
||||
_webpageDescriptionOptions.maxw = st::msgMaxWidth - st::msgPadding.left() - st::msgPadding.right() - st::webPageLeft;
|
||||
_webpageDescriptionOptions.maxh = st::webPageDescriptionFont->height * 3;
|
||||
}
|
||||
|
||||
bool needReSetInlineResultDocument(const MTPMessageMedia &media, DocumentData *existing) {
|
||||
if (media.type() == mtpc_messageMediaDocument) {
|
||||
auto &mediaDocument = media.c_messageMediaDocument();
|
||||
@@ -121,10 +90,6 @@ int32 gifMaxStatusWidth(DocumentData *document) {
|
||||
|
||||
} // namespace
|
||||
|
||||
void HistoryInitMedia() {
|
||||
initTextOptions();
|
||||
}
|
||||
|
||||
TextWithEntities WithCaptionSelectedText(
|
||||
const QString &attachType,
|
||||
const Text &caption,
|
||||
@@ -175,7 +140,7 @@ void HistoryFileMedia::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool
|
||||
if (active && !dataLoaded()) {
|
||||
ensureAnimation();
|
||||
_animation->a_thumbOver.start([this] { thumbAnimationCallback(); }, 0., 1., st::msgFileOverDuration);
|
||||
} else if (!active && _animation) {
|
||||
} else if (!active && _animation && !dataLoaded()) {
|
||||
_animation->a_thumbOver.start([this] { thumbAnimationCallback(); }, 1., 0., st::msgFileOverDuration);
|
||||
}
|
||||
}
|
||||
@@ -263,7 +228,7 @@ HistoryPhoto::HistoryPhoto(
|
||||
_caption.setText(
|
||||
st::messageTextStyle,
|
||||
caption + _parent->skipBlock(),
|
||||
itemTextNoMonoOptions(_parent));
|
||||
Ui::ItemTextNoMonoOptions(_parent));
|
||||
}
|
||||
init();
|
||||
}
|
||||
@@ -839,10 +804,13 @@ bool HistoryPhoto::needsBubble() const {
|
||||
}
|
||||
|
||||
Storage::SharedMediaTypesMask HistoryPhoto::sharedMediaTypes() const {
|
||||
using Type = Storage::SharedMediaType;
|
||||
if (_parent->toHistoryMessage()) {
|
||||
return Storage::SharedMediaType::Photo;
|
||||
return Storage::SharedMediaTypesMask{}
|
||||
.added(Type::Photo)
|
||||
.added(Type::PhotoVideo);
|
||||
}
|
||||
return Storage::SharedMediaType::ChatPhoto;
|
||||
return Type::ChatPhoto;
|
||||
}
|
||||
|
||||
ImagePtr HistoryPhoto::replyPreview() {
|
||||
@@ -861,7 +829,7 @@ HistoryVideo::HistoryVideo(
|
||||
_caption.setText(
|
||||
st::messageTextStyle,
|
||||
caption + _parent->skipBlock(),
|
||||
itemTextNoMonoOptions(_parent));
|
||||
Ui::ItemTextNoMonoOptions(_parent));
|
||||
}
|
||||
|
||||
setDocumentLinks(_data, parent);
|
||||
@@ -1264,8 +1232,8 @@ void HistoryVideo::validateGroupedCache(
|
||||
const auto pixSize = Ui::GetImageScaleSizeForGeometry(
|
||||
{ originalWidth, originalHeight },
|
||||
{ width, height });
|
||||
const auto pixWidth = pixSize.width();
|
||||
const auto pixHeight = pixSize.height();
|
||||
const auto pixWidth = pixSize.width() * cIntRetinaFactor();
|
||||
const auto pixHeight = pixSize.height() * cIntRetinaFactor();
|
||||
const auto &image = _data->thumb;
|
||||
|
||||
*cacheKey = key;
|
||||
@@ -1305,7 +1273,10 @@ bool HistoryVideo::needsBubble() const {
|
||||
}
|
||||
|
||||
Storage::SharedMediaTypesMask HistoryVideo::sharedMediaTypes() const {
|
||||
return Storage::SharedMediaType::Video;
|
||||
using Type = Storage::SharedMediaType;
|
||||
return Storage::SharedMediaTypesMask{}
|
||||
.added(Type::Video)
|
||||
.added(Type::PhotoVideo);
|
||||
}
|
||||
|
||||
void HistoryVideo::updateStatusText() const {
|
||||
@@ -1380,7 +1351,10 @@ HistoryDocument::HistoryDocument(
|
||||
setStatusSize(FileStatusSizeReady);
|
||||
|
||||
if (auto captioned = Get<HistoryDocumentCaptioned>()) {
|
||||
captioned->_caption.setText(st::messageTextStyle, caption + _parent->skipBlock(), itemTextNoMonoOptions(_parent));
|
||||
captioned->_caption.setText(
|
||||
st::messageTextStyle,
|
||||
caption + _parent->skipBlock(),
|
||||
Ui::ItemTextNoMonoOptions(_parent));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2168,7 +2142,10 @@ HistoryGif::HistoryGif(
|
||||
setStatusSize(FileStatusSizeReady);
|
||||
|
||||
if (!caption.isEmpty() && !_data->isVideoMessage()) {
|
||||
_caption.setText(st::messageTextStyle, caption + _parent->skipBlock(), itemTextNoMonoOptions(_parent));
|
||||
_caption.setText(
|
||||
st::messageTextStyle,
|
||||
caption + _parent->skipBlock(),
|
||||
Ui::ItemTextNoMonoOptions(_parent));
|
||||
}
|
||||
|
||||
_data->thumb->load();
|
||||
@@ -3304,7 +3281,10 @@ HistoryContact::HistoryContact(not_null<HistoryItem*> parent, int32 userId, cons
|
||||
, _fname(first)
|
||||
, _lname(last)
|
||||
, _phone(App::formatPhone(phone)) {
|
||||
_name.setText(st::semiboldTextStyle, lng_full_name(lt_first_name, first, lt_last_name, last).trimmed(), _textNameOptions);
|
||||
_name.setText(
|
||||
st::semiboldTextStyle,
|
||||
lng_full_name(lt_first_name, first, lt_last_name, last).trimmed(),
|
||||
Ui::NameTextOptions());
|
||||
_phonew = st::normalFont->width(_phone);
|
||||
}
|
||||
|
||||
@@ -3587,21 +3567,6 @@ TextWithEntities HistoryCall::selectedText(TextSelection selection) const {
|
||||
|
||||
namespace {
|
||||
|
||||
QString siteNameFromUrl(const QString &url) {
|
||||
QUrl u(url);
|
||||
QString pretty = u.isValid() ? u.toDisplayString() : url;
|
||||
QRegularExpressionMatch m = QRegularExpression(qsl("^[a-zA-Z0-9]+://")).match(pretty);
|
||||
if (m.hasMatch()) pretty = pretty.mid(m.capturedLength());
|
||||
int32 slash = pretty.indexOf('/');
|
||||
if (slash > 0) pretty = pretty.mid(0, slash);
|
||||
QStringList components = pretty.split('.', QString::SkipEmptyParts);
|
||||
if (components.size() >= 2) {
|
||||
components = components.mid(components.size() - 2);
|
||||
return components.at(0).at(0).toUpper() + components.at(0).mid(1) + '.' + components.at(1);
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
|
||||
int32 articleThumbWidth(PhotoData *thumb, int32 height) {
|
||||
int32 w = thumb->medium->width(), h = thumb->medium->height();
|
||||
return qMax(qMin(height * w / h, height), 1);
|
||||
@@ -3643,6 +3608,18 @@ void HistoryWebPage::initDimensions() {
|
||||
_maxw = _minh = _height = 0;
|
||||
return;
|
||||
}
|
||||
const auto versionChanged = (_dataVersion != _data->version);
|
||||
if (versionChanged) {
|
||||
_dataVersion = _data->version;
|
||||
_openl = nullptr;
|
||||
if (_attach) {
|
||||
_attach->detachFromParent();
|
||||
_attach = nullptr;
|
||||
}
|
||||
_title = Text(st::msgMinWidth - st::webPageLeft);
|
||||
_description = Text(st::msgMinWidth - st::webPageLeft);
|
||||
_siteNameWidth = 0;
|
||||
}
|
||||
auto lineHeight = unitedLineHeight();
|
||||
|
||||
if (!_openl && !_data->url.isEmpty()) {
|
||||
@@ -3651,9 +3628,6 @@ void HistoryWebPage::initDimensions() {
|
||||
|
||||
// init layout
|
||||
auto title = TextUtilities::SingleLine(_data->title.isEmpty() ? _data->author : _data->title);
|
||||
if (!_data->description.text.isEmpty() && title.isEmpty() && _data->siteName.isEmpty() && !_data->url.isEmpty()) {
|
||||
_data->siteName = siteNameFromUrl(_data->url);
|
||||
}
|
||||
if (!_data->document && _data->photo && _data->type != WebPagePhoto && _data->type != WebPageVideo) {
|
||||
if (_data->type == WebPageProfile) {
|
||||
_asArticle = true;
|
||||
@@ -3670,7 +3644,7 @@ void HistoryWebPage::initDimensions() {
|
||||
}
|
||||
|
||||
// init attach
|
||||
if (!_asArticle && !_attach) {
|
||||
if (!_attach && !_asArticle) {
|
||||
if (_data->document) {
|
||||
if (_data->document->sticker()) {
|
||||
_attach = std::make_unique<HistorySticker>(_parent, _data->document);
|
||||
@@ -3684,6 +3658,9 @@ void HistoryWebPage::initDimensions() {
|
||||
} else if (_data->photo) {
|
||||
_attach = std::make_unique<HistoryPhoto>(_parent, _data->photo, QString());
|
||||
}
|
||||
if (_attach) {
|
||||
_attach->attachToParent();
|
||||
}
|
||||
}
|
||||
|
||||
auto textFloatsAroundInfo = !_asArticle && !_attach && isBubbleBottom();
|
||||
@@ -3695,12 +3672,6 @@ void HistoryWebPage::initDimensions() {
|
||||
if (textFloatsAroundInfo) {
|
||||
text.text += _parent->skipBlock();
|
||||
}
|
||||
auto opts = &_webpageDescriptionOptions;
|
||||
if (_data->siteName == qstr("Twitter")) {
|
||||
opts = &_twitterDescriptionOptions;
|
||||
} else if (_data->siteName == qstr("Instagram")) {
|
||||
opts = &_instagramDescriptionOptions;
|
||||
}
|
||||
if (isLogEntryOriginal()) {
|
||||
// Fix layout for small bubbles (narrow media caption edit log entries).
|
||||
_description = Text(st::minPhotoSize
|
||||
@@ -3708,13 +3679,19 @@ void HistoryWebPage::initDimensions() {
|
||||
- st::msgPadding.right()
|
||||
- st::webPageLeft);
|
||||
}
|
||||
_description.setMarkedText(st::webPageDescriptionStyle, text, *opts);
|
||||
_description.setMarkedText(
|
||||
st::webPageDescriptionStyle,
|
||||
text,
|
||||
Ui::WebpageTextDescriptionOptions(_data->siteName));
|
||||
}
|
||||
if (_title.isEmpty() && !title.isEmpty()) {
|
||||
if (textFloatsAroundInfo && _description.isEmpty()) {
|
||||
title += _parent->skipBlock();
|
||||
}
|
||||
_title.setText(st::webPageTitleStyle, title, _webpageTitleOptions);
|
||||
_title.setText(
|
||||
st::webPageTitleStyle,
|
||||
title,
|
||||
Ui::WebpageTextTitleOptions());
|
||||
}
|
||||
if (!_siteNameWidth && !_data->siteName.isEmpty()) {
|
||||
_siteNameWidth = st::webPageTitleFont->width(_data->siteName);
|
||||
@@ -4257,11 +4234,17 @@ void HistoryGame::initDimensions() {
|
||||
auto marked = TextWithEntities { text };
|
||||
auto parseFlags = TextParseLinks | TextParseMultiline | TextParseRichText;
|
||||
TextUtilities::ParseEntities(marked, parseFlags);
|
||||
_description.setMarkedText(st::webPageDescriptionStyle, marked, _webpageDescriptionOptions);
|
||||
_description.setMarkedText(
|
||||
st::webPageDescriptionStyle,
|
||||
marked,
|
||||
Ui::WebpageTextDescriptionOptions());
|
||||
}
|
||||
}
|
||||
if (_title.isEmpty() && !title.isEmpty()) {
|
||||
_title.setText(st::webPageTitleStyle, title, _webpageTitleOptions);
|
||||
_title.setText(
|
||||
st::webPageTitleStyle,
|
||||
title,
|
||||
Ui::WebpageTextTitleOptions());
|
||||
}
|
||||
|
||||
// init dimensions
|
||||
@@ -4523,7 +4506,10 @@ TextSelection HistoryGame::adjustSelection(TextSelection selection, TextSelectTy
|
||||
}
|
||||
|
||||
bool HistoryGame::consumeMessageText(const TextWithEntities &textWithEntities) {
|
||||
_description.setMarkedText(st::webPageDescriptionStyle, textWithEntities, itemTextOptions(_parent));
|
||||
_description.setMarkedText(
|
||||
st::webPageDescriptionStyle,
|
||||
textWithEntities,
|
||||
Ui::ItemTextOptions(_parent));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -4696,7 +4682,10 @@ void HistoryInvoice::fillFromData(const MTPDmessageMediaInvoice &data) {
|
||||
};
|
||||
statusText.entities.push_back(EntityInText(EntityInTextBold, 0, statusText.text.size()));
|
||||
statusText.text += ' ' + labelText().toUpper();
|
||||
_status.setMarkedText(st::defaultTextStyle, statusText, itemTextOptions(_parent));
|
||||
_status.setMarkedText(
|
||||
st::defaultTextStyle,
|
||||
statusText,
|
||||
Ui::ItemTextOptions(_parent));
|
||||
|
||||
_receiptMsgId = data.has_receipt_msg_id() ? data.vreceipt_msg_id.v : 0;
|
||||
|
||||
@@ -4706,11 +4695,17 @@ void HistoryInvoice::fillFromData(const MTPDmessageMediaInvoice &data) {
|
||||
auto marked = TextWithEntities { description };
|
||||
auto parseFlags = TextParseLinks | TextParseMultiline | TextParseRichText;
|
||||
TextUtilities::ParseEntities(marked, parseFlags);
|
||||
_description.setMarkedText(st::webPageDescriptionStyle, marked, _webpageDescriptionOptions);
|
||||
_description.setMarkedText(
|
||||
st::webPageDescriptionStyle,
|
||||
marked,
|
||||
Ui::WebpageTextDescriptionOptions());
|
||||
}
|
||||
auto title = TextUtilities::SingleLine(qs(data.vtitle));
|
||||
if (!title.isEmpty()) {
|
||||
_title.setText(st::webPageTitleStyle, title, _webpageTitleOptions);
|
||||
_title.setText(
|
||||
st::webPageTitleStyle,
|
||||
title,
|
||||
Ui::WebpageTextTitleOptions());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5025,13 +5020,19 @@ HistoryLocation::HistoryLocation(not_null<HistoryItem*> parent, const LocationCo
|
||||
, _description(st::msgMinWidth)
|
||||
, _link(std::make_shared<LocationClickHandler>(coords)) {
|
||||
if (!title.isEmpty()) {
|
||||
_title.setText(st::webPageTitleStyle, TextUtilities::Clean(title), _webpageTitleOptions);
|
||||
_title.setText(
|
||||
st::webPageTitleStyle,
|
||||
TextUtilities::Clean(title),
|
||||
Ui::WebpageTextTitleOptions());
|
||||
}
|
||||
if (!description.isEmpty()) {
|
||||
auto marked = TextWithEntities { TextUtilities::Clean(description) };
|
||||
auto parseFlags = TextParseLinks | TextParseMultiline | TextParseRichText;
|
||||
TextUtilities::ParseEntities(marked, parseFlags);
|
||||
_description.setMarkedText(st::webPageDescriptionStyle, marked, _webpageDescriptionOptions);
|
||||
_description.setMarkedText(
|
||||
st::webPageDescriptionStyle,
|
||||
marked,
|
||||
Ui::WebpageTextDescriptionOptions());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -43,7 +43,6 @@ namespace Ui {
|
||||
class EmptyUserpic;
|
||||
} // namespace Ui
|
||||
|
||||
void HistoryInitMedia();
|
||||
TextWithEntities WithCaptionSelectedText(
|
||||
const QString &attachType,
|
||||
const Text &caption,
|
||||
@@ -943,13 +942,15 @@ private:
|
||||
std::unique_ptr<HistoryMedia> _attach;
|
||||
|
||||
bool _asArticle = false;
|
||||
int32 _titleLines, _descriptionLines;
|
||||
int _dataVersion = -1;
|
||||
int _titleLines = 0;
|
||||
int _descriptionLines = 0;
|
||||
|
||||
Text _title, _description;
|
||||
int32 _siteNameWidth = 0;
|
||||
int _siteNameWidth = 0;
|
||||
|
||||
QString _duration;
|
||||
int32 _durationWidth = 0;
|
||||
int _durationWidth = 0;
|
||||
|
||||
int16 _pixw = 0;
|
||||
int16 _pixh = 0;
|
||||
|
||||
@@ -33,6 +33,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "boxes/share_box.h"
|
||||
#include "boxes/confirm_box.h"
|
||||
#include "ui/toast/toast.h"
|
||||
#include "ui/text_options.h"
|
||||
#include "messenger.h"
|
||||
#include "styles/style_dialogs.h"
|
||||
#include "styles/style_widgets.h"
|
||||
@@ -138,15 +139,14 @@ int KeyboardStyle::minButtonWidth(
|
||||
return result;
|
||||
}
|
||||
|
||||
inline void initTextOptions() {
|
||||
_historySrvOptions.dir = _textNameOptions.dir = _textDlgOptions.dir = cLangDir();
|
||||
_textDlgOptions.maxw = st::columnMaximalWidthLeft * 2;
|
||||
}
|
||||
|
||||
QString AdminBadgeText() {
|
||||
return lang(lng_admin_badge);
|
||||
}
|
||||
|
||||
QString FastReplyText() {
|
||||
return lang(lng_fast_reply);
|
||||
}
|
||||
|
||||
style::color FromNameFg(not_null<PeerData*> peer, bool selected) {
|
||||
if (selected) {
|
||||
const style::color colors[] = {
|
||||
@@ -409,10 +409,6 @@ void FastShareMessage(not_null<HistoryItem*> item) {
|
||||
std::move(filterCallback)));
|
||||
}
|
||||
|
||||
void HistoryInitMessages() {
|
||||
initTextOptions();
|
||||
}
|
||||
|
||||
base::lambda<void(ChannelData*, MsgId)> HistoryDependentItemCallback(
|
||||
const FullMsgId &msgId) {
|
||||
return [dependent = msgId](ChannelData *channel, MsgId msgId) {
|
||||
@@ -1159,10 +1155,16 @@ void HistoryMessage::initDimensions() {
|
||||
if (via && !forwarded) {
|
||||
namew += st::msgServiceFont->spacew + via->maxWidth;
|
||||
}
|
||||
const auto replyWidth = hasFastReply()
|
||||
? st::msgFont->width(FastReplyText())
|
||||
: 0;
|
||||
if (_flags & MTPDmessage_ClientFlag::f_has_admin_badge) {
|
||||
auto badgeWidth = st::msgServiceFont->width(
|
||||
const auto badgeWidth = st::msgFont->width(
|
||||
AdminBadgeText());
|
||||
namew += st::msgPadding.right() + badgeWidth;
|
||||
namew += st::msgPadding.right()
|
||||
+ std::max(badgeWidth, replyWidth);
|
||||
} else if (replyWidth) {
|
||||
namew += st::msgPadding.right() + replyWidth;
|
||||
}
|
||||
accumulate_max(_maxw, namew);
|
||||
} else if (via && !forwarded) {
|
||||
@@ -1224,6 +1226,15 @@ bool HistoryMessage::hasFromName() const {
|
||||
&& (!history()->peer->isUser() || history()->peer->isSelf());
|
||||
}
|
||||
|
||||
bool HistoryMessage::hasFastReply() const {
|
||||
return !hasOutLayout()
|
||||
&& (history()->peer->isChat() || history()->peer->isMegagroup());
|
||||
}
|
||||
|
||||
bool HistoryMessage::displayFastReply() const {
|
||||
return hasFastReply() && history()->peer->canWrite();
|
||||
}
|
||||
|
||||
QRect HistoryMessage::countGeometry() const {
|
||||
auto maxwidth = qMin(st::msgMaxWidth, _maxw);
|
||||
if (_media && _media->currentWidth() < maxwidth) {
|
||||
@@ -1260,10 +1271,14 @@ QRect HistoryMessage::countGeometry() const {
|
||||
}
|
||||
|
||||
void HistoryMessage::fromNameUpdated(int32 width) const {
|
||||
const auto replyWidth = hasFastReply()
|
||||
? st::msgFont->width(FastReplyText())
|
||||
: 0;
|
||||
if (_flags & MTPDmessage_ClientFlag::f_has_admin_badge) {
|
||||
auto badgeWidth = st::msgServiceFont->width(
|
||||
AdminBadgeText());
|
||||
width -= st::msgPadding.right() + badgeWidth;
|
||||
const auto badgeWidth = st::msgFont->width(AdminBadgeText());
|
||||
width -= st::msgPadding.right() + std::max(badgeWidth, replyWidth);
|
||||
} else if (replyWidth) {
|
||||
width -= st::msgPadding.right() + replyWidth;
|
||||
}
|
||||
_fromNameVersion = displayFrom()->nameVersion;
|
||||
if (!Has<HistoryMessageForwarded>()) {
|
||||
@@ -1501,9 +1516,18 @@ void HistoryMessage::setText(const TextWithEntities &textWithEntities) {
|
||||
} else {
|
||||
auto mediaOnBottom = (_media && _media->isDisplayed() && _media->isBubbleBottom()) || Has<HistoryMessageLogEntryOriginal>();
|
||||
if (mediaOnBottom) {
|
||||
_text.setMarkedText(st::messageTextStyle, textWithEntities, itemTextOptions(this));
|
||||
_text.setMarkedText(
|
||||
st::messageTextStyle,
|
||||
textWithEntities,
|
||||
Ui::ItemTextOptions(this));
|
||||
} else {
|
||||
_text.setMarkedText(st::messageTextStyle, { textWithEntities.text + skipBlock(), textWithEntities.entities }, itemTextOptions(this));
|
||||
_text.setMarkedText(
|
||||
st::messageTextStyle,
|
||||
{
|
||||
textWithEntities.text + skipBlock(),
|
||||
textWithEntities.entities
|
||||
},
|
||||
Ui::ItemTextOptions(this));
|
||||
}
|
||||
_textWidth = -1;
|
||||
_textHeight = 0;
|
||||
@@ -1511,7 +1535,10 @@ void HistoryMessage::setText(const TextWithEntities &textWithEntities) {
|
||||
}
|
||||
|
||||
void HistoryMessage::setEmptyText() {
|
||||
_text.setMarkedText(st::messageTextStyle, { QString(), EntitiesInText() }, itemTextOptions(this));
|
||||
_text.setMarkedText(
|
||||
st::messageTextStyle,
|
||||
{ QString(), EntitiesInText() },
|
||||
Ui::ItemTextOptions(this));
|
||||
|
||||
_textWidth = -1;
|
||||
_textHeight = 0;
|
||||
@@ -1875,18 +1902,28 @@ void HistoryMessage::drawRightAction(Painter &p, int left, int top, int outerWid
|
||||
}
|
||||
}
|
||||
|
||||
void HistoryMessage::paintFromName(Painter &p, QRect &trect, bool selected) const {
|
||||
void HistoryMessage::paintFromName(
|
||||
Painter &p,
|
||||
QRect &trect,
|
||||
bool selected) const {
|
||||
if (displayFromName()) {
|
||||
auto badgeWidth = [&] {
|
||||
const auto badgeWidth = [&] {
|
||||
if (_flags & MTPDmessage_ClientFlag::f_has_admin_badge) {
|
||||
return st::msgServiceFont->width(AdminBadgeText());
|
||||
return st::msgFont->width(AdminBadgeText());
|
||||
}
|
||||
return 0;
|
||||
}();
|
||||
const auto replyWidth = [&] {
|
||||
if (App::hoveredItem() == this && displayFastReply()) {
|
||||
return st::msgFont->width(FastReplyText());
|
||||
}
|
||||
return 0;
|
||||
}();
|
||||
const auto rightWidth = replyWidth ? replyWidth : badgeWidth;
|
||||
auto availableLeft = trect.left();
|
||||
auto availableWidth = trect.width();
|
||||
if (badgeWidth) {
|
||||
availableWidth -= st::msgPadding.right() + badgeWidth;
|
||||
if (rightWidth) {
|
||||
availableWidth -= st::msgPadding.right() + rightWidth;
|
||||
}
|
||||
|
||||
p.setFont(st::msgNameFont);
|
||||
@@ -1910,13 +1947,15 @@ void HistoryMessage::paintFromName(Painter &p, QRect &trect, bool selected) cons
|
||||
availableLeft += skipWidth;
|
||||
availableWidth -= skipWidth;
|
||||
}
|
||||
if (badgeWidth) {
|
||||
if (rightWidth) {
|
||||
p.setPen(selected ? st::msgInDateFgSelected : st::msgInDateFg);
|
||||
p.setFont(st::msgFont);
|
||||
p.setFont(ClickHandler::showAsActive(_fastReplyLink)
|
||||
? st::msgFont->underline()
|
||||
: st::msgFont);
|
||||
p.drawText(
|
||||
trect.left() + trect.width() - badgeWidth,
|
||||
trect.left() + trect.width() - rightWidth,
|
||||
trect.top() + st::msgFont->ascent,
|
||||
AdminBadgeText());
|
||||
replyWidth ? FastReplyText() : AdminBadgeText());
|
||||
}
|
||||
trect.setY(trect.y() + st::msgNameFont->height);
|
||||
}
|
||||
@@ -2264,6 +2303,20 @@ ClickHandlerPtr HistoryMessage::rightActionLink() const {
|
||||
return _rightActionLink;
|
||||
}
|
||||
|
||||
ClickHandlerPtr HistoryMessage::fastReplyLink() const {
|
||||
if (!_fastReplyLink) {
|
||||
const auto itemId = fullId();
|
||||
_fastReplyLink = std::make_shared<LambdaClickHandler>([=] {
|
||||
if (const auto item = App::histItemById(itemId)) {
|
||||
if (const auto main = App::main()) {
|
||||
main->replyToItem(item);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
return _fastReplyLink;
|
||||
}
|
||||
|
||||
// Forward to _media.
|
||||
void HistoryMessage::updatePressed(QPoint point) {
|
||||
if (!_media) return;
|
||||
@@ -2320,15 +2373,40 @@ bool HistoryMessage::getStateFromName(
|
||||
QRect &trect,
|
||||
not_null<HistoryTextState*> outResult) const {
|
||||
if (displayFromName()) {
|
||||
const auto replyWidth = [&] {
|
||||
if (App::hoveredItem() == this && displayFastReply()) {
|
||||
return st::msgFont->width(FastReplyText());
|
||||
}
|
||||
return 0;
|
||||
}();
|
||||
if (replyWidth
|
||||
&& point.x() >= trect.left() + trect.width() - replyWidth
|
||||
&& point.x() < trect.left() + trect.width() + st::msgPadding.right()
|
||||
&& point.y() >= trect.top() - st::msgPadding.top()
|
||||
&& point.y() < trect.top() + st::msgServiceFont->height) {
|
||||
outResult->link = fastReplyLink();
|
||||
return true;
|
||||
}
|
||||
if (point.y() >= trect.top() && point.y() < trect.top() + st::msgNameFont->height) {
|
||||
auto availableLeft = trect.left();
|
||||
auto availableWidth = trect.width();
|
||||
if (replyWidth) {
|
||||
availableWidth -= st::msgPadding.right() + replyWidth;
|
||||
}
|
||||
auto user = displayFrom();
|
||||
if (point.x() >= trect.left() && point.x() < trect.left() + trect.width() && point.x() < trect.left() + user->nameText.maxWidth()) {
|
||||
if (point.x() >= availableLeft
|
||||
&& point.x() < availableLeft + availableWidth
|
||||
&& point.x() < availableLeft + user->nameText.maxWidth()) {
|
||||
outResult->link = user->openLink();
|
||||
return true;
|
||||
}
|
||||
auto forwarded = Get<HistoryMessageForwarded>();
|
||||
auto via = Get<HistoryMessageVia>();
|
||||
if (via && !forwarded && point.x() >= trect.left() + author()->nameText.maxWidth() + st::msgServiceFont->spacew && point.x() < trect.left() + user->nameText.maxWidth() + st::msgServiceFont->spacew + via->width) {
|
||||
if (via
|
||||
&& !forwarded
|
||||
&& point.x() >= availableLeft + author()->nameText.maxWidth() + st::msgServiceFont->spacew
|
||||
&& point.x() < availableLeft + availableWidth
|
||||
&& point.x() < availableLeft + user->nameText.maxWidth() + st::msgServiceFont->spacew + via->width) {
|
||||
outResult->link = via->link;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -24,7 +24,6 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
|
||||
struct HistoryMessageEdited;
|
||||
|
||||
void HistoryInitMessages();
|
||||
base::lambda<void(ChannelData*, MsgId)> HistoryDependentItemCallback(
|
||||
const FullMsgId &msgId);
|
||||
MTPDmessage::Flags NewMessageFlags(not_null<PeerData*> peer);
|
||||
@@ -170,6 +169,8 @@ public:
|
||||
if (isAttachedToPrevious()) return false;
|
||||
return true;
|
||||
}
|
||||
bool hasFastReply() const;
|
||||
bool displayFastReply() const;
|
||||
bool displayForwardedFrom() const;
|
||||
bool uploading() const;
|
||||
bool displayRightAction() const override;
|
||||
@@ -361,17 +362,19 @@ private:
|
||||
bool displayFastShare() const;
|
||||
bool displayGoToOriginal() const;
|
||||
|
||||
QString _timeText;
|
||||
int _timeWidth = 0;
|
||||
|
||||
mutable ClickHandlerPtr _rightActionLink;
|
||||
mutable int32 _fromNameVersion = 0;
|
||||
|
||||
struct CreateConfig;
|
||||
void createComponentsHelper(MTPDmessage::Flags flags, MsgId replyTo, UserId viaBotId, const QString &postAuthor, const MTPReplyMarkup &markup);
|
||||
void createComponents(const CreateConfig &config);
|
||||
|
||||
void updateMediaInBubbleState();
|
||||
void updateAdminBadgeState();
|
||||
ClickHandlerPtr fastReplyLink() const;
|
||||
|
||||
QString _timeText;
|
||||
int _timeWidth = 0;
|
||||
|
||||
mutable ClickHandlerPtr _rightActionLink;
|
||||
mutable ClickHandlerPtr _fastReplyLink;
|
||||
mutable int32 _fromNameVersion = 0;
|
||||
|
||||
};
|
||||
|
||||
@@ -30,6 +30,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "auth_session.h"
|
||||
#include "window/notifications_manager.h"
|
||||
#include "storage/storage_shared_media.h"
|
||||
#include "ui/text_options.h"
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -37,13 +38,6 @@ constexpr auto kPinnedMessageTextLimit = 16;
|
||||
|
||||
} // namespace
|
||||
|
||||
TextParseOptions _historySrvOptions = {
|
||||
TextParseLinks | TextParseMentions | TextParseHashtags/* | TextParseMultiline*/ | TextParseRichText, // flags
|
||||
0, // maxw
|
||||
0, // maxh
|
||||
Qt::LayoutDirectionAuto, // lang-dependent
|
||||
};
|
||||
|
||||
void HistoryService::setMessageByAction(const MTPmessageAction &action) {
|
||||
auto prepareChatAddUserText = [this](const MTPDmessageActionChatAddUser &action) {
|
||||
auto result = PreparedText {};
|
||||
@@ -495,7 +489,10 @@ QString HistoryService::inReplyText() const {
|
||||
}
|
||||
|
||||
void HistoryService::setServiceText(const PreparedText &prepared) {
|
||||
_text.setText(st::serviceTextStyle, prepared.text, _historySrvOptions);
|
||||
_text.setText(
|
||||
st::serviceTextStyle,
|
||||
prepared.text,
|
||||
Ui::ItemTextServiceOptions());
|
||||
auto linkIndex = 0;
|
||||
for_const (auto &link, prepared.links) {
|
||||
// Link indices start with 1.
|
||||
|
||||
@@ -182,5 +182,3 @@ private:
|
||||
static PreparedText GenerateText(not_null<History*> history, not_null<UserData*> inviter);
|
||||
|
||||
};
|
||||
|
||||
extern TextParseOptions _historySrvOptions;
|
||||
|
||||
@@ -74,6 +74,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "observer_peer.h"
|
||||
#include "base/qthelp_regex.h"
|
||||
#include "ui/widgets/popup_menu.h"
|
||||
#include "ui/text_options.h"
|
||||
#include "auth_session.h"
|
||||
#include "window/themes/window_theme.h"
|
||||
#include "window/notifications_manager.h"
|
||||
@@ -110,10 +111,8 @@ void ActivateWindowDelayed(not_null<Window::Controller*> controller) {
|
||||
const auto window = controller->window();
|
||||
const auto weak = make_weak(window.get());
|
||||
window->activateWindow();
|
||||
crl::on_main([=] {
|
||||
if (weak) {
|
||||
weak->activateWindow();
|
||||
}
|
||||
crl::on_main(window, [=] {
|
||||
window->activateWindow();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -382,7 +381,7 @@ bool HistoryHider::offerPeer(PeerId peer) {
|
||||
return false;
|
||||
}
|
||||
|
||||
_toText.setText(st::boxLabelStyle, phrase, _textNameOptions);
|
||||
_toText.setText(st::boxLabelStyle, phrase, Ui::NameTextOptions());
|
||||
_toTextWidth = _toText.maxWidth();
|
||||
if (_toTextWidth > _box.width() - st::boxPadding.left() - st::boxLayerButtonPadding.right()) {
|
||||
_toTextWidth = _box.width() - st::boxPadding.left() - st::boxLayerButtonPadding.right();
|
||||
@@ -2776,7 +2775,7 @@ void HistoryWidget::saveEditMsg() {
|
||||
WebPageId webPageId = _previewCancelled ? CancelledWebPageId : ((_previewData && _previewData->pendingTill >= 0) ? _previewData->id : 0);
|
||||
|
||||
auto &textWithTags = _field->getTextWithTags();
|
||||
auto prepareFlags = itemTextOptions(_history, App::self()).flags;
|
||||
auto prepareFlags = Ui::ItemTextOptions(_history, App::self()).flags;
|
||||
auto sending = TextWithEntities();
|
||||
auto left = TextWithEntities { textWithTags.text, ConvertTextTagsToEntities(textWithTags.tags) };
|
||||
TextUtilities::PrepareForSending(left, prepareFlags);
|
||||
@@ -3664,7 +3663,10 @@ void HistoryWidget::onKbToggle(bool manual) {
|
||||
_kbReplyTo = (_peer->isChat() || _peer->isChannel() || _keyboard->forceReply()) ? App::histItemById(_keyboard->forMsgId()) : 0;
|
||||
if (_kbReplyTo && !_editMsgId && !_replyToId && fieldEnabled) {
|
||||
updateReplyToName();
|
||||
_replyEditMsgText.setText(st::messageTextStyle, TextUtilities::Clean(_kbReplyTo->inReplyText()), _textDlgOptions);
|
||||
_replyEditMsgText.setText(
|
||||
st::messageTextStyle,
|
||||
TextUtilities::Clean(_kbReplyTo->inReplyText()),
|
||||
Ui::DialogTextOptions());
|
||||
_fieldBarCancel->show();
|
||||
updateMouseTracking();
|
||||
}
|
||||
@@ -3683,7 +3685,10 @@ void HistoryWidget::onKbToggle(bool manual) {
|
||||
_kbReplyTo = (_peer->isChat() || _peer->isChannel() || _keyboard->forceReply()) ? App::histItemById(_keyboard->forMsgId()) : 0;
|
||||
if (_kbReplyTo && !_editMsgId && !_replyToId) {
|
||||
updateReplyToName();
|
||||
_replyEditMsgText.setText(st::messageTextStyle, TextUtilities::Clean(_kbReplyTo->inReplyText()), _textDlgOptions);
|
||||
_replyEditMsgText.setText(
|
||||
st::messageTextStyle,
|
||||
TextUtilities::Clean(_kbReplyTo->inReplyText()),
|
||||
Ui::DialogTextOptions());
|
||||
_fieldBarCancel->show();
|
||||
updateMouseTracking();
|
||||
}
|
||||
@@ -4090,7 +4095,9 @@ bool HistoryWidget::confirmSendingFiles(
|
||||
const auto urls = data->urls();
|
||||
for (const auto &url : urls) {
|
||||
if (url.isLocalFile()) {
|
||||
confirmSendingFiles(urls, compressed, insertTextOnCancel);
|
||||
// Don't insert list of filenames on cancel.
|
||||
const auto emptyTextOnCancel = QString();
|
||||
confirmSendingFiles(urls, compressed, emptyTextOnCancel);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -4877,7 +4884,10 @@ void HistoryWidget::updateBotKeyboard(History *h, bool force) {
|
||||
_kbReplyTo = (_peer->isChat() || _peer->isChannel() || _keyboard->forceReply()) ? App::histItemById(_keyboard->forMsgId()) : 0;
|
||||
if (_kbReplyTo && !_replyToId) {
|
||||
updateReplyToName();
|
||||
_replyEditMsgText.setText(st::messageTextStyle, TextUtilities::Clean(_kbReplyTo->inReplyText()), _textDlgOptions);
|
||||
_replyEditMsgText.setText(
|
||||
st::messageTextStyle,
|
||||
TextUtilities::Clean(_kbReplyTo->inReplyText()),
|
||||
Ui::DialogTextOptions());
|
||||
_fieldBarCancel->show();
|
||||
updateMouseTracking();
|
||||
}
|
||||
@@ -5034,6 +5044,19 @@ void HistoryWidget::keyPressEvent(QKeyEvent *e) {
|
||||
} else if (e->key() == Qt::Key_Down) {
|
||||
if (!(e->modifiers() & (Qt::ShiftModifier | Qt::MetaModifier | Qt::ControlModifier))) {
|
||||
_scroll->keyPressEvent(e);
|
||||
} else if ((e->modifiers() & (Qt::ShiftModifier | Qt::MetaModifier | Qt::ControlModifier)) == Qt::ControlModifier) {
|
||||
if (_history && _history->lastMsg && !_editMsgId) {
|
||||
if (_replyToId) {
|
||||
HistoryItem *item = App::histItemById(_history->channelId(), _replyToId)->nextItem();
|
||||
if (item) App::contextItem(item);
|
||||
else { cancelReply(); return; }
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
Ui::showPeerHistory(_peer, App::contextItem()->id);
|
||||
onReplyToMessage();
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else if (e->key() == Qt::Key_Up) {
|
||||
if (!(e->modifiers() & (Qt::ShiftModifier | Qt::MetaModifier | Qt::ControlModifier))) {
|
||||
@@ -5045,6 +5068,20 @@ void HistoryWidget::keyPressEvent(QKeyEvent *e) {
|
||||
}
|
||||
}
|
||||
_scroll->keyPressEvent(e);
|
||||
} else if ((e->modifiers() & (Qt::ShiftModifier | Qt::MetaModifier | Qt::ControlModifier)) == Qt::ControlModifier) {
|
||||
if (_history && _history->lastMsg && !_editMsgId) {
|
||||
if (_replyToId) {
|
||||
HistoryItem *item = App::histItemById(_history->channelId(), _replyToId);
|
||||
App::contextItem(item->previousItem());
|
||||
} else {
|
||||
App::contextItem(_history->lastMsg);
|
||||
}
|
||||
if (App::contextItem()) {
|
||||
Ui::showPeerHistory(_peer, App::contextItem()->id);
|
||||
onReplyToMessage();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else if (e->key() == Qt::Key_Return || e->key() == Qt::Key_Enter) {
|
||||
onListEnterPressed();
|
||||
@@ -5211,7 +5248,10 @@ void HistoryWidget::updatePinnedBar(bool force) {
|
||||
_pinnedBar->msg = App::histItemById(_history->channelId(), _pinnedBar->msgId);
|
||||
}
|
||||
if (_pinnedBar->msg) {
|
||||
_pinnedBar->text.setText(st::messageTextStyle, TextUtilities::Clean(_pinnedBar->msg->notificationText()), _textDlgOptions);
|
||||
_pinnedBar->text.setText(
|
||||
st::messageTextStyle,
|
||||
TextUtilities::Clean(_pinnedBar->msg->notificationText()),
|
||||
Ui::DialogTextOptions());
|
||||
update();
|
||||
} else if (force) {
|
||||
if (auto channel = _peer ? _peer->asChannel() : nullptr) {
|
||||
@@ -5508,7 +5548,10 @@ void HistoryWidget::onReplyToMessage() {
|
||||
} else {
|
||||
_replyEditMsg = to;
|
||||
_replyToId = to->id;
|
||||
_replyEditMsgText.setText(st::messageTextStyle, TextUtilities::Clean(_replyEditMsg->inReplyText()), _textDlgOptions);
|
||||
_replyEditMsgText.setText(
|
||||
st::messageTextStyle,
|
||||
TextUtilities::Clean(_replyEditMsg->inReplyText()),
|
||||
Ui::DialogTextOptions());
|
||||
|
||||
updateBotKeyboard();
|
||||
|
||||
@@ -5864,13 +5907,19 @@ void HistoryWidget::updatePreview() {
|
||||
_fieldBarCancel->show();
|
||||
updateMouseTracking();
|
||||
if (_previewData->pendingTill) {
|
||||
_previewTitle.setText(st::msgNameStyle, lang(lng_preview_loading), _textNameOptions);
|
||||
_previewTitle.setText(
|
||||
st::msgNameStyle,
|
||||
lang(lng_preview_loading),
|
||||
Ui::NameTextOptions());
|
||||
#ifndef OS_MAC_OLD
|
||||
auto linkText = _previewLinks.splitRef(' ').at(0).toString();
|
||||
#else // OS_MAC_OLD
|
||||
auto linkText = _previewLinks.split(' ').at(0);
|
||||
#endif // OS_MAC_OLD
|
||||
_previewDescription.setText(st::messageTextStyle, TextUtilities::Clean(linkText), _textDlgOptions);
|
||||
_previewDescription.setText(
|
||||
st::messageTextStyle,
|
||||
TextUtilities::Clean(linkText),
|
||||
Ui::DialogTextOptions());
|
||||
|
||||
int32 t = (_previewData->pendingTill - unixtime()) * 1000;
|
||||
if (t <= 0) t = 1;
|
||||
@@ -5901,8 +5950,14 @@ void HistoryWidget::updatePreview() {
|
||||
title = lang(lng_attach_photo);
|
||||
}
|
||||
}
|
||||
_previewTitle.setText(st::msgNameStyle, title, _textNameOptions);
|
||||
_previewDescription.setText(st::messageTextStyle, TextUtilities::Clean(desc), _textDlgOptions);
|
||||
_previewTitle.setText(
|
||||
st::msgNameStyle,
|
||||
title,
|
||||
Ui::NameTextOptions());
|
||||
_previewDescription.setText(
|
||||
st::messageTextStyle,
|
||||
TextUtilities::Clean(desc),
|
||||
Ui::DialogTextOptions());
|
||||
}
|
||||
} else if (!readyToForward() && !replyToId() && !_editMsgId) {
|
||||
_fieldBarCancel->hide();
|
||||
@@ -6122,7 +6177,10 @@ void HistoryWidget::updateReplyEditTexts(bool force) {
|
||||
_replyEditMsg = App::histItemById(_channel, _editMsgId ? _editMsgId : _replyToId);
|
||||
}
|
||||
if (_replyEditMsg) {
|
||||
_replyEditMsgText.setText(st::messageTextStyle, TextUtilities::Clean(_replyEditMsg->inReplyText()), _textDlgOptions);
|
||||
_replyEditMsgText.setText(
|
||||
st::messageTextStyle,
|
||||
TextUtilities::Clean(_replyEditMsg->inReplyText()),
|
||||
Ui::DialogTextOptions());
|
||||
|
||||
updateBotKeyboard();
|
||||
|
||||
@@ -6181,8 +6239,11 @@ void HistoryWidget::updateForwardingTexts() {
|
||||
text = lng_forward_messages(lt_count, count);
|
||||
}
|
||||
}
|
||||
_toForwardFrom.setText(st::msgNameStyle, from, _textNameOptions);
|
||||
_toForwardText.setText(st::messageTextStyle, TextUtilities::Clean(text), _textDlgOptions);
|
||||
_toForwardFrom.setText(st::msgNameStyle, from, Ui::NameTextOptions());
|
||||
_toForwardText.setText(
|
||||
st::messageTextStyle,
|
||||
TextUtilities::Clean(text),
|
||||
Ui::DialogTextOptions());
|
||||
_toForwardNameVersion = version;
|
||||
}
|
||||
|
||||
@@ -6201,7 +6262,10 @@ void HistoryWidget::checkForwardingInfo() {
|
||||
void HistoryWidget::updateReplyToName() {
|
||||
if (_editMsgId) return;
|
||||
if (!_replyEditMsg && (_replyToId || !_kbReplyTo)) return;
|
||||
_replyToName.setText(st::msgNameStyle, App::peerName((_replyEditMsg ? _replyEditMsg : _kbReplyTo)->author()), _textNameOptions);
|
||||
_replyToName.setText(
|
||||
st::msgNameStyle,
|
||||
App::peerName((_replyEditMsg ? _replyEditMsg : _kbReplyTo)->author()),
|
||||
Ui::NameTextOptions());
|
||||
_replyToNameVersion = (_replyEditMsg ? _replyEditMsg : _kbReplyTo)->author()->nameVersion;
|
||||
}
|
||||
|
||||
|
||||
@@ -204,7 +204,7 @@ void Gif::paint(Painter &p, const QRect &clip, const PaintContext *context) cons
|
||||
if (radial) {
|
||||
p.setOpacity(1);
|
||||
QRect rinner(inner.marginsRemoved(QMargins(st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine)));
|
||||
_animation->radial.draw(p, rinner, st::msgFileRadialLine, st::msgInBg);
|
||||
_animation->radial.draw(p, rinner, st::msgFileRadialLine, st::historyFileThumbRadialFg);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -745,7 +745,7 @@ void File::paint(Painter &p, const QRect &clip, const PaintContext *context) con
|
||||
|
||||
if (radial) {
|
||||
auto radialCircle = inner.marginsRemoved(QMargins(st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine));
|
||||
_animation->radial.draw(p, radialCircle, st::msgFileRadialLine, st::msgInBg);
|
||||
_animation->radial.draw(p, radialCircle, st::msgFileRadialLine, st::historyFileInRadialFg);
|
||||
}
|
||||
|
||||
auto icon = ([showPause, radial, document] {
|
||||
@@ -1253,7 +1253,7 @@ void Game::paint(Painter &p, const QRect &clip, const PaintContext *context) con
|
||||
if (radial) {
|
||||
p.setOpacity(1);
|
||||
QRect rinner(inner.marginsRemoved(QMargins(st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine)));
|
||||
_radial->draw(p, rinner, st::msgFileRadialLine, st::msgInBg);
|
||||
_radial->draw(p, rinner, st::msgFileRadialLine, st::historyFileThumbRadialFg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -32,65 +32,6 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "media/media_audio.h"
|
||||
#include "storage/localstorage.h"
|
||||
|
||||
TextParseOptions _textNameOptions = {
|
||||
0, // flags
|
||||
4096, // maxw
|
||||
1, // maxh
|
||||
Qt::LayoutDirectionAuto, // lang-dependent
|
||||
};
|
||||
TextParseOptions _textDlgOptions = {
|
||||
TextParseRichText, // flags
|
||||
0, // maxw is style-dependent
|
||||
1, // maxh
|
||||
Qt::LayoutDirectionAuto, // lang-dependent
|
||||
};
|
||||
TextParseOptions _historyTextOptions = {
|
||||
TextParseLinks | TextParseMentions | TextParseHashtags | TextParseMultiline | TextParseRichText | TextParseMarkdown, // flags
|
||||
0, // maxw
|
||||
0, // maxh
|
||||
Qt::LayoutDirectionAuto, // dir
|
||||
};
|
||||
TextParseOptions _historyBotOptions = {
|
||||
TextParseLinks | TextParseMentions | TextParseHashtags | TextParseBotCommands | TextParseMultiline | TextParseRichText | TextParseMarkdown, // flags
|
||||
0, // maxw
|
||||
0, // maxh
|
||||
Qt::LayoutDirectionAuto, // dir
|
||||
};
|
||||
TextParseOptions _historyTextNoMonoOptions = {
|
||||
TextParseLinks | TextParseMentions | TextParseHashtags | TextParseMultiline | TextParseRichText, // flags
|
||||
0, // maxw
|
||||
0, // maxh
|
||||
Qt::LayoutDirectionAuto, // dir
|
||||
};
|
||||
TextParseOptions _historyBotNoMonoOptions = {
|
||||
TextParseLinks | TextParseMentions | TextParseHashtags | TextParseBotCommands | TextParseMultiline | TextParseRichText, // flags
|
||||
0, // maxw
|
||||
0, // maxh
|
||||
Qt::LayoutDirectionAuto, // dir
|
||||
};
|
||||
|
||||
const TextParseOptions &itemTextOptions(History *h, PeerData *f) {
|
||||
if ((h->peer->isUser() && h->peer->asUser()->botInfo) || (f->isUser() && f->asUser()->botInfo) || (h->peer->isChat() && h->peer->asChat()->botStatus >= 0) || (h->peer->isMegagroup() && h->peer->asChannel()->mgInfo->botStatus >= 0)) {
|
||||
return _historyBotOptions;
|
||||
}
|
||||
return _historyTextOptions;
|
||||
}
|
||||
|
||||
const TextParseOptions &itemTextOptions(const HistoryItem *item) {
|
||||
return itemTextOptions(item->history(), item->author());
|
||||
}
|
||||
|
||||
const TextParseOptions &itemTextNoMonoOptions(History *h, PeerData *f) {
|
||||
if ((h->peer->isUser() && h->peer->asUser()->botInfo) || (f->isUser() && f->asUser()->botInfo) || (h->peer->isChat() && h->peer->asChat()->botStatus >= 0) || (h->peer->isMegagroup() && h->peer->asChannel()->mgInfo->botStatus >= 0)) {
|
||||
return _historyBotNoMonoOptions;
|
||||
}
|
||||
return _historyTextNoMonoOptions;
|
||||
}
|
||||
|
||||
const TextParseOptions &itemTextNoMonoOptions(const HistoryItem *item) {
|
||||
return itemTextNoMonoOptions(item->history(), item->author());
|
||||
}
|
||||
|
||||
QString formatSizeText(qint64 size) {
|
||||
if (size >= 1024 * 1024) { // more than 1 mb
|
||||
qint64 sizeTenthMb = (size * 10 / (1024 * 1024));
|
||||
|
||||
@@ -58,14 +58,6 @@ inline bool IsGroupItemSelection(
|
||||
: selection;
|
||||
}
|
||||
|
||||
extern TextParseOptions _textNameOptions, _textDlgOptions;
|
||||
extern TextParseOptions _historyTextOptions, _historyBotOptions, _historyTextNoMonoOptions, _historyBotNoMonoOptions;
|
||||
|
||||
const TextParseOptions &itemTextOptions(History *h, PeerData *f);
|
||||
const TextParseOptions &itemTextOptions(const HistoryItem *item);
|
||||
const TextParseOptions &itemTextNoMonoOptions(History *h, PeerData *f);
|
||||
const TextParseOptions &itemTextNoMonoOptions(const HistoryItem *item);
|
||||
|
||||
enum RoundCorners {
|
||||
SmallMaskCorners = 0x00, // for images
|
||||
LargeMaskCorners,
|
||||
|
||||
@@ -39,6 +39,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "ui/widgets/dropdown_menu.h"
|
||||
#include "ui/focus_persister.h"
|
||||
#include "ui/resize_area.h"
|
||||
#include "ui/text_options.h"
|
||||
#include "ui/toast/toast.h"
|
||||
#include "chat_helpers/message_field.h"
|
||||
#include "chat_helpers/stickers.h"
|
||||
@@ -682,6 +683,14 @@ bool MainWidget::shareUrl(
|
||||
return true;
|
||||
}
|
||||
|
||||
void MainWidget::replyToItem(not_null<HistoryItem*> item) {
|
||||
if (_history->peer() == item->history()->peer
|
||||
|| _history->peer() == item->history()->peer->migrateTo()) {
|
||||
App::contextItem(item);
|
||||
_history->onReplyToMessage();
|
||||
}
|
||||
}
|
||||
|
||||
bool MainWidget::onInlineSwitchChosen(const PeerId &peer, const QString &botAndQuery) {
|
||||
PeerData *p = App::peer(peer);
|
||||
if (!peer || !p->canWrite()) {
|
||||
@@ -1456,7 +1465,7 @@ void MainWidget::sendMessage(const MessageToSend &message) {
|
||||
|
||||
auto sending = TextWithEntities();
|
||||
auto left = TextWithEntities { textWithTags.text, ConvertTextTagsToEntities(textWithTags.tags) };
|
||||
auto prepareFlags = itemTextOptions(history, App::self()).flags;
|
||||
auto prepareFlags = Ui::ItemTextOptions(history, App::self()).flags;
|
||||
TextUtilities::PrepareForSending(left, prepareFlags);
|
||||
|
||||
HistoryItem *lastMessage = nullptr;
|
||||
@@ -1744,7 +1753,7 @@ void MainWidget::createPlayer() {
|
||||
_player->shownValue()
|
||||
) | rpl::start_with_next(
|
||||
[this] { playerHeightUpdated(); },
|
||||
lifetime());
|
||||
_player->lifetime());
|
||||
_player->entity()->setCloseCallback([this] { closeBothPlayers(); });
|
||||
_playerVolume.create(this);
|
||||
_player->entity()->volumeWidgetCreated(_playerVolume);
|
||||
@@ -1768,6 +1777,10 @@ void MainWidget::createPlayer() {
|
||||
}
|
||||
|
||||
void MainWidget::playerHeightUpdated() {
|
||||
if (!_player) {
|
||||
// Player could be already "destroyDelayed", but still handle events.
|
||||
return;
|
||||
}
|
||||
auto playerHeight = _player->contentHeight();
|
||||
if (playerHeight != _playerHeight) {
|
||||
_contentScrollAddToY += playerHeight - _playerHeight;
|
||||
|
||||
@@ -191,6 +191,7 @@ public:
|
||||
not_null<PeerData*> peer,
|
||||
const QString &url,
|
||||
const QString &text);
|
||||
void replyToItem(not_null<HistoryItem*> item);
|
||||
bool onInlineSwitchChosen(const PeerId &peer, const QString &botAndQuery);
|
||||
bool onSendPaths(const PeerId &peer);
|
||||
void onFilesOrForwardDrop(const PeerId &peer, const QMimeData *data);
|
||||
|
||||
@@ -51,7 +51,6 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "window/themes/window_theme.h"
|
||||
#include "window/themes/window_theme_warning.h"
|
||||
#include "window/window_main_menu.h"
|
||||
#include "base/task_queue.h"
|
||||
#include "auth_session.h"
|
||||
#include "window/window_controller.h"
|
||||
|
||||
@@ -535,24 +534,23 @@ void MainWindow::themeUpdated(const Window::Theme::BackgroundUpdate &data) {
|
||||
_testingThemeWarning->setGeometry(rect());
|
||||
_testingThemeWarning->setHiddenCallback([this] { _testingThemeWarning.destroyDelayed(); });
|
||||
}
|
||||
|
||||
base::TaskQueue::Main().Put(base::lambda_guarded(this, [this] {
|
||||
crl::on_main(this, [=] {
|
||||
if (_testingThemeWarning) {
|
||||
_testingThemeWarning->showAnimated();
|
||||
}
|
||||
}));
|
||||
});
|
||||
} else if (data.type == Type::RevertingTheme || data.type == Type::ApplyingTheme) {
|
||||
if (_testingThemeWarning) {
|
||||
if (_testingThemeWarning->isHidden()) {
|
||||
_testingThemeWarning.destroy();
|
||||
} else {
|
||||
base::TaskQueue::Main().Put(base::lambda_guarded(this, [this] {
|
||||
crl::on_main(this, [=] {
|
||||
if (_testingThemeWarning) {
|
||||
_testingThemeWarning->hideAnimated();
|
||||
_testingThemeWarning = nullptr;
|
||||
}
|
||||
setInnerFocus();
|
||||
}));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "media/media_audio_loaders.h"
|
||||
#include "media/media_audio_track.h"
|
||||
#include "platform/platform_audio.h"
|
||||
#include "base/task_queue.h"
|
||||
#include "messenger.h"
|
||||
|
||||
#include <AL/al.h>
|
||||
#include <AL/alc.h>
|
||||
@@ -230,27 +230,35 @@ bool AttachToDevice() {
|
||||
emit m->faderOnTimer();
|
||||
}
|
||||
|
||||
base::TaskQueue::Main().Put([] {
|
||||
Current().reattachTracks();
|
||||
crl::on_main([] {
|
||||
if (Messenger::InstancePointer()) {
|
||||
Current().reattachTracks();
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
void ScheduleDetachFromDeviceSafe() {
|
||||
base::TaskQueue::Main().Put([] {
|
||||
Current().scheduleDetachFromDevice();
|
||||
crl::on_main([] {
|
||||
if (Messenger::InstancePointer()) {
|
||||
Current().scheduleDetachFromDevice();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void ScheduleDetachIfNotUsedSafe() {
|
||||
base::TaskQueue::Main().Put([] {
|
||||
Current().scheduleDetachIfNotUsed();
|
||||
crl::on_main([] {
|
||||
if (Messenger::InstancePointer()) {
|
||||
Current().scheduleDetachIfNotUsed();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void StopDetachIfNotUsedSafe() {
|
||||
base::TaskQueue::Main().Put([] {
|
||||
Current().stopDetachIfNotUsed();
|
||||
crl::on_main([] {
|
||||
if (Messenger::InstancePointer()) {
|
||||
Current().stopDetachIfNotUsed();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
663
Telegram/SourceFiles/media/view/media_view_group_thumbs.cpp
Normal file
663
Telegram/SourceFiles/media/view/media_view_group_thumbs.cpp
Normal file
@@ -0,0 +1,663 @@
|
||||
/*
|
||||
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-2017 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#include "media/view/media_view_group_thumbs.h"
|
||||
|
||||
#include "data/data_shared_media.h"
|
||||
#include "data/data_user_photos.h"
|
||||
#include "data/data_photo.h"
|
||||
#include "data/data_document.h"
|
||||
#include "history/history_media.h"
|
||||
#include "styles/style_mediaview.h"
|
||||
|
||||
namespace Media {
|
||||
namespace View {
|
||||
namespace {
|
||||
|
||||
constexpr auto kThumbDuration = TimeMs(150);
|
||||
|
||||
int Round(float64 value) {
|
||||
return int(std::round(value));
|
||||
}
|
||||
|
||||
using Context = GroupThumbs::Context;
|
||||
using Key = GroupThumbs::Key;
|
||||
|
||||
Context ComputeContext(const SharedMediaWithLastSlice &slice, int index) {
|
||||
Expects(index >= 0 && index < slice.size());
|
||||
|
||||
const auto value = slice[index];
|
||||
if (const auto photo = base::get_if<not_null<PhotoData*>>(&value)) {
|
||||
if (const auto peer = (*photo)->peer) {
|
||||
return peer->id;
|
||||
}
|
||||
return base::none;
|
||||
} else if (const auto msgId = base::get_if<FullMsgId>(&value)) {
|
||||
if (const auto item = App::histItemById(*msgId)) {
|
||||
if (!item->toHistoryMessage()) {
|
||||
return item->history()->peer->id;
|
||||
} else if (const auto groupId = item->groupId()) {
|
||||
return groupId;
|
||||
}
|
||||
}
|
||||
return base::none;
|
||||
}
|
||||
Unexpected("Variant in ComputeContext(SharedMediaWithLastSlice::Value)");
|
||||
}
|
||||
|
||||
Context ComputeContext(const UserPhotosSlice &slice, int index) {
|
||||
return peerFromUser(slice.key().userId);
|
||||
}
|
||||
|
||||
Key ComputeKey(const SharedMediaWithLastSlice &slice, int index) {
|
||||
Expects(index >= 0 && index < slice.size());
|
||||
|
||||
const auto value = slice[index];
|
||||
if (const auto photo = base::get_if<not_null<PhotoData*>>(&value)) {
|
||||
return (*photo)->id;
|
||||
} else if (const auto msgId = base::get_if<FullMsgId>(&value)) {
|
||||
return *msgId;
|
||||
}
|
||||
Unexpected("Variant in ComputeContext(SharedMediaWithLastSlice::Value)");
|
||||
}
|
||||
|
||||
Key ComputeKey(const UserPhotosSlice &slice, int index) {
|
||||
return slice[index];
|
||||
}
|
||||
|
||||
int ComputeThumbsLimit(int availableWidth) {
|
||||
const auto singleWidth = st::mediaviewGroupWidth
|
||||
+ 2 * st::mediaviewGroupSkip;
|
||||
const auto currentWidth = st::mediaviewGroupWidthMax
|
||||
+ 2 * st::mediaviewGroupSkipCurrent;
|
||||
const auto skipForAnimation = 2 * singleWidth;
|
||||
const auto leftWidth = availableWidth
|
||||
- currentWidth
|
||||
- skipForAnimation;
|
||||
return std::max(leftWidth / (2 * singleWidth), 1);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class GroupThumbs::Thumb {
|
||||
public:
|
||||
enum class State {
|
||||
Unknown,
|
||||
Current,
|
||||
Alive,
|
||||
Dying,
|
||||
};
|
||||
|
||||
Thumb(Key key, ImagePtr image, base::lambda<void()> handler);
|
||||
|
||||
int leftToUpdate() const;
|
||||
int rightToUpdate() const;
|
||||
|
||||
void animateToLeft(not_null<Thumb*> next);
|
||||
void animateToRight(not_null<Thumb*> prev);
|
||||
|
||||
void setState(State state);
|
||||
State state() const;
|
||||
bool removed() const;
|
||||
|
||||
void paint(Painter &p, int x, int y, int outerWidth, float64 progress);
|
||||
ClickHandlerPtr getState(QPoint point) const;
|
||||
|
||||
private:
|
||||
QSize wantedPixSize() const;
|
||||
void validateImage();
|
||||
int currentLeft() const;
|
||||
int currentWidth() const;
|
||||
int finalLeft() const;
|
||||
int finalWidth() const;
|
||||
void animateTo(int left, int width);
|
||||
|
||||
ClickHandlerPtr _link;
|
||||
const Key _key;
|
||||
ImagePtr _image;
|
||||
State _state = State::Alive;
|
||||
QPixmap _full;
|
||||
int _fullWidth = 0;
|
||||
bool _hiding = false;
|
||||
|
||||
anim::value _left = { 0. };
|
||||
anim::value _width = { 0. };
|
||||
anim::value _opacity = { 0., 1. };
|
||||
|
||||
};
|
||||
|
||||
GroupThumbs::Thumb::Thumb(
|
||||
Key key,
|
||||
ImagePtr image,
|
||||
base::lambda<void()> handler)
|
||||
: _key(key)
|
||||
, _image(image) {
|
||||
_link = std::make_shared<LambdaClickHandler>(std::move(handler));
|
||||
_fullWidth = std::min(
|
||||
wantedPixSize().width(),
|
||||
st::mediaviewGroupWidthMax);
|
||||
validateImage();
|
||||
}
|
||||
|
||||
QSize GroupThumbs::Thumb::wantedPixSize() const {
|
||||
const auto originalWidth = std::max(_image->width(), 1);
|
||||
const auto originalHeight = std::max(_image->height(), 1);
|
||||
const auto pixHeight = st::mediaviewGroupHeight;
|
||||
const auto pixWidth = originalWidth * pixHeight / originalHeight;
|
||||
return { pixWidth, pixHeight };
|
||||
}
|
||||
|
||||
void GroupThumbs::Thumb::validateImage() {
|
||||
if (!_full.isNull()) {
|
||||
return;
|
||||
}
|
||||
_image->load();
|
||||
if (!_image->loaded()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto pixSize = wantedPixSize();
|
||||
if (pixSize.width() > st::mediaviewGroupWidthMax) {
|
||||
const auto originalWidth = _image->width();
|
||||
const auto originalHeight = _image->height();
|
||||
const auto takeWidth = originalWidth * st::mediaviewGroupWidthMax
|
||||
/ pixSize.width();
|
||||
const auto original = _image->pixNoCache().toImage();
|
||||
_full = App::pixmapFromImageInPlace(original.copy(
|
||||
(originalWidth - takeWidth) / 2,
|
||||
0,
|
||||
takeWidth,
|
||||
originalHeight
|
||||
).scaled(
|
||||
st::mediaviewGroupWidthMax * cIntRetinaFactor(),
|
||||
pixSize.height() * cIntRetinaFactor(),
|
||||
Qt::IgnoreAspectRatio,
|
||||
Qt::SmoothTransformation));
|
||||
} else {
|
||||
_full = _image->pixNoCache(
|
||||
pixSize.width() * cIntRetinaFactor(),
|
||||
pixSize.height() * cIntRetinaFactor(),
|
||||
Images::Option::Smooth);
|
||||
}
|
||||
}
|
||||
|
||||
int GroupThumbs::Thumb::leftToUpdate() const {
|
||||
return Round(std::min(_left.from(), _left.to()));
|
||||
}
|
||||
|
||||
int GroupThumbs::Thumb::rightToUpdate() const {
|
||||
return Round(std::max(
|
||||
_left.from() + _width.from(),
|
||||
_left.to() + _width.to()));
|
||||
}
|
||||
|
||||
int GroupThumbs::Thumb::currentLeft() const {
|
||||
return Round(_left.current());
|
||||
}
|
||||
|
||||
int GroupThumbs::Thumb::currentWidth() const {
|
||||
return Round(_width.current());
|
||||
}
|
||||
|
||||
int GroupThumbs::Thumb::finalLeft() const {
|
||||
return Round(_left.to());
|
||||
}
|
||||
|
||||
int GroupThumbs::Thumb::finalWidth() const {
|
||||
return Round(_width.to());
|
||||
}
|
||||
|
||||
void GroupThumbs::Thumb::setState(State state) {
|
||||
const auto isNewThumb = (_state == State::Alive);
|
||||
_state = state;
|
||||
if (_state == State::Current) {
|
||||
if (isNewThumb) {
|
||||
_opacity = anim::value(1.);
|
||||
_left = anim::value(-_fullWidth / 2);
|
||||
_width = anim::value(_fullWidth);
|
||||
} else {
|
||||
_opacity.start(1.);
|
||||
}
|
||||
_hiding = false;
|
||||
animateTo(-_fullWidth / 2, _fullWidth);
|
||||
} else if (_state == State::Alive) {
|
||||
_opacity.start(0.7);
|
||||
_hiding = false;
|
||||
} else if (_state == State::Dying) {
|
||||
_opacity.start(0.);
|
||||
_hiding = true;
|
||||
_left.restart();
|
||||
_width.restart();
|
||||
}
|
||||
}
|
||||
|
||||
void GroupThumbs::Thumb::animateTo(int left, int width) {
|
||||
_left.start(left);
|
||||
_width.start(width);
|
||||
}
|
||||
|
||||
void GroupThumbs::Thumb::animateToLeft(not_null<Thumb*> next) {
|
||||
const auto width = st::mediaviewGroupWidth;
|
||||
if (_state == State::Alive) {
|
||||
// New item animation, start exactly from the next, move only.
|
||||
_left = anim::value(next->currentLeft() - width);
|
||||
_width = anim::value(width);
|
||||
} else if (_state == State::Unknown) {
|
||||
// Existing item animation.
|
||||
setState(State::Alive);
|
||||
}
|
||||
const auto skip1 = st::mediaviewGroupSkip;
|
||||
const auto skip2 = (next->state() == State::Current)
|
||||
? st::mediaviewGroupSkipCurrent
|
||||
: st::mediaviewGroupSkip;
|
||||
animateTo(next->finalLeft() - width - skip1 - skip2, width);
|
||||
}
|
||||
|
||||
void GroupThumbs::Thumb::animateToRight(not_null<Thumb*> prev) {
|
||||
const auto width = st::mediaviewGroupWidth;
|
||||
if (_state == State::Alive) {
|
||||
// New item animation, start exactly from the next, move only.
|
||||
_left = anim::value(prev->currentLeft() + prev->currentWidth());
|
||||
_width = anim::value(width);
|
||||
} else if (_state == State::Unknown) {
|
||||
// Existing item animation.
|
||||
setState(State::Alive);
|
||||
}
|
||||
const auto skip1 = st::mediaviewGroupSkip;
|
||||
const auto skip2 = (prev->state() == State::Current)
|
||||
? st::mediaviewGroupSkipCurrent
|
||||
: st::mediaviewGroupSkip;
|
||||
animateTo(prev->finalLeft() + prev->finalWidth() + skip1 + skip2, width);
|
||||
}
|
||||
|
||||
auto GroupThumbs::Thumb::state() const -> State {
|
||||
return _state;
|
||||
}
|
||||
|
||||
bool GroupThumbs::Thumb::removed() const {
|
||||
return (_state == State::Dying) && _hiding && !_opacity.current();
|
||||
}
|
||||
|
||||
void GroupThumbs::Thumb::paint(
|
||||
Painter &p,
|
||||
int x,
|
||||
int y,
|
||||
int outerWidth,
|
||||
float64 progress) {
|
||||
validateImage();
|
||||
|
||||
_opacity.update(progress, anim::linear);
|
||||
_left.update(progress, anim::linear);
|
||||
_width.update(progress, anim::linear);
|
||||
|
||||
const auto left = x + currentLeft();
|
||||
const auto width = currentWidth();
|
||||
const auto opacity = p.opacity();
|
||||
p.setOpacity(_opacity.current() * opacity);
|
||||
if (width == _fullWidth) {
|
||||
p.drawPixmap(left, y, _full);
|
||||
} else {
|
||||
const auto takeWidth = width * cIntRetinaFactor();
|
||||
const auto from = QRect(
|
||||
(_full.width() - takeWidth) / 2,
|
||||
0,
|
||||
takeWidth,
|
||||
_full.height());
|
||||
const auto to = QRect(left, y, width, st::mediaviewGroupHeight);
|
||||
p.drawPixmap(to, _full, from);
|
||||
}
|
||||
p.setOpacity(opacity);
|
||||
}
|
||||
|
||||
ClickHandlerPtr GroupThumbs::Thumb::getState(QPoint point) const {
|
||||
if (_state != State::Alive) {
|
||||
return nullptr;
|
||||
}
|
||||
const auto left = finalLeft();
|
||||
const auto width = finalWidth();
|
||||
return QRect(left, 0, width, st::mediaviewGroupHeight).contains(point)
|
||||
? _link
|
||||
: nullptr;
|
||||
}
|
||||
|
||||
GroupThumbs::GroupThumbs(Context context)
|
||||
: _context(context) {
|
||||
}
|
||||
|
||||
void GroupThumbs::updateContext(Context context) {
|
||||
if (_context != context) {
|
||||
clear();
|
||||
_context = context;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Slice>
|
||||
void GroupThumbs::RefreshFromSlice(
|
||||
std::unique_ptr<GroupThumbs> &instance,
|
||||
const Slice &slice,
|
||||
int index,
|
||||
int availableWidth) {
|
||||
const auto context = ComputeContext(slice, index);
|
||||
if (instance) {
|
||||
instance->updateContext(context);
|
||||
}
|
||||
if (!context) {
|
||||
if (instance) {
|
||||
instance->resizeToWidth(availableWidth);
|
||||
}
|
||||
return;
|
||||
}
|
||||
const auto limit = ComputeThumbsLimit(availableWidth);
|
||||
const auto from = [&] {
|
||||
const auto edge = std::max(index - limit, 0);
|
||||
for (auto result = index; result != edge; --result) {
|
||||
if (ComputeContext(slice, result - 1) != context) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return edge;
|
||||
}();
|
||||
const auto till = [&] {
|
||||
const auto edge = std::min(index + limit + 1, slice.size());
|
||||
for (auto result = index + 1; result != edge; ++result) {
|
||||
if (ComputeContext(slice, result) != context) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return edge;
|
||||
}();
|
||||
if (from + 1 < till) {
|
||||
if (!instance) {
|
||||
instance = std::make_unique<GroupThumbs>(context);
|
||||
}
|
||||
instance->fillItems(slice, from, index, till);
|
||||
instance->resizeToWidth(availableWidth);
|
||||
} else if (instance) {
|
||||
instance->clear();
|
||||
instance->resizeToWidth(availableWidth);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Slice>
|
||||
void GroupThumbs::fillItems(
|
||||
const Slice &slice,
|
||||
int from,
|
||||
int index,
|
||||
int till) {
|
||||
Expects(from <= index);
|
||||
Expects(index < till);
|
||||
Expects(from + 1 < till);
|
||||
|
||||
|
||||
const auto current = (index - from);
|
||||
const auto old = base::take(_items);
|
||||
|
||||
markCacheStale();
|
||||
_items.reserve(till - from);
|
||||
for (auto i = from; i != till; ++i) {
|
||||
_items.push_back(validateCacheEntry(ComputeKey(slice, i)));
|
||||
}
|
||||
animateAliveItems(current);
|
||||
fillDyingItems(old);
|
||||
startDelayedAnimation();
|
||||
}
|
||||
|
||||
void GroupThumbs::animateAliveItems(int current) {
|
||||
Expects(current >= 0 && current < _items.size());
|
||||
|
||||
_items[current]->setState(Thumb::State::Current);
|
||||
for (auto i = current; i != 0;) {
|
||||
const auto prev = _items[i];
|
||||
const auto item = _items[--i];
|
||||
item->animateToLeft(prev);
|
||||
}
|
||||
for (auto i = current + 1; i != _items.size(); ++i) {
|
||||
const auto prev = _items[i - 1];
|
||||
const auto item = _items[i];
|
||||
item->animateToRight(prev);
|
||||
}
|
||||
}
|
||||
|
||||
void GroupThumbs::fillDyingItems(const std::vector<not_null<Thumb*>> &old) {
|
||||
_dying.reserve(_cache.size() - _items.size());
|
||||
animatePreviouslyAlive(old);
|
||||
markRestAsDying();
|
||||
}
|
||||
|
||||
void GroupThumbs::markRestAsDying() {
|
||||
_dying.reserve(_cache.size() - _items.size());
|
||||
for (const auto &cacheItem : _cache) {
|
||||
const auto &thumb = cacheItem.second;
|
||||
const auto state = thumb->state();
|
||||
if (state == Thumb::State::Unknown) {
|
||||
markAsDying(thumb.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GroupThumbs::markAsDying(not_null<Thumb*> thumb) {
|
||||
thumb->setState(Thumb::State::Dying);
|
||||
_dying.push_back(thumb.get());
|
||||
}
|
||||
|
||||
void GroupThumbs::animatePreviouslyAlive(
|
||||
const std::vector<not_null<Thumb*>> &old) {
|
||||
auto toRight = false;
|
||||
for (auto i = 0; i != old.size(); ++i) {
|
||||
const auto item = old[i];
|
||||
if (item->state() == Thumb::State::Unknown) {
|
||||
if (toRight) {
|
||||
markAsDying(item);
|
||||
item->animateToRight(old[i - 1]);
|
||||
}
|
||||
} else if (!toRight) {
|
||||
for (auto j = i; j != 0;) {
|
||||
const auto next = old[j];
|
||||
const auto prev = old[--j];
|
||||
markAsDying(prev);
|
||||
prev->animateToLeft(next);
|
||||
}
|
||||
toRight = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto GroupThumbs::createThumb(Key key) -> std::unique_ptr<Thumb> {
|
||||
if (const auto photoId = base::get_if<PhotoId>(&key)) {
|
||||
const auto photo = App::photo(*photoId);
|
||||
return createThumb(key, photo->date ? photo->thumb : ImagePtr());
|
||||
} else if (const auto msgId = base::get_if<FullMsgId>(&key)) {
|
||||
if (const auto item = App::histItemById(*msgId)) {
|
||||
if (const auto media = item->getMedia()) {
|
||||
if (const auto photo = media->getPhoto()) {
|
||||
return createThumb(key, photo->thumb);
|
||||
} else if (const auto document = media->getDocument()) {
|
||||
return createThumb(key, document->thumb);
|
||||
}
|
||||
}
|
||||
}
|
||||
return createThumb(key, ImagePtr());
|
||||
}
|
||||
Unexpected("Value of Key in GroupThumbs::createThumb()");
|
||||
}
|
||||
|
||||
auto GroupThumbs::createThumb(Key key, ImagePtr image)
|
||||
-> std::unique_ptr<Thumb> {
|
||||
const auto weak = base::make_weak(this);
|
||||
return std::make_unique<Thumb>(key, image, [=] {
|
||||
if (const auto strong = weak.get()) {
|
||||
strong->_activateStream.fire_copy(key);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
auto GroupThumbs::validateCacheEntry(Key key) -> not_null<Thumb*> {
|
||||
const auto i = _cache.find(key);
|
||||
return (i != _cache.end())
|
||||
? i->second.get()
|
||||
: _cache.emplace(key, createThumb(key)).first->second.get();
|
||||
}
|
||||
|
||||
void GroupThumbs::markCacheStale() {
|
||||
while (!_dying.empty()) {
|
||||
_dying.pop_back();
|
||||
}
|
||||
for (const auto &cacheItem : _cache) {
|
||||
const auto &thumb = cacheItem.second;
|
||||
thumb->setState(Thumb::State::Unknown);
|
||||
}
|
||||
}
|
||||
|
||||
void GroupThumbs::Refresh(
|
||||
std::unique_ptr<GroupThumbs> &instance,
|
||||
const SharedMediaWithLastSlice &slice,
|
||||
int index,
|
||||
int availableWidth) {
|
||||
RefreshFromSlice(instance, slice, index, availableWidth);
|
||||
}
|
||||
|
||||
void GroupThumbs::Refresh(
|
||||
std::unique_ptr<GroupThumbs> &instance,
|
||||
const UserPhotosSlice &slice,
|
||||
int index,
|
||||
int availableWidth) {
|
||||
RefreshFromSlice(instance, slice, index, availableWidth);
|
||||
}
|
||||
|
||||
void GroupThumbs::clear() {
|
||||
if (_items.empty()) {
|
||||
return;
|
||||
}
|
||||
base::take(_items);
|
||||
markCacheStale();
|
||||
markRestAsDying();
|
||||
startDelayedAnimation();
|
||||
}
|
||||
|
||||
void GroupThumbs::startDelayedAnimation() {
|
||||
_animation.finish();
|
||||
_waitingForAnimationStart = true;
|
||||
countUpdatedRect();
|
||||
}
|
||||
|
||||
void GroupThumbs::resizeToWidth(int newWidth) {
|
||||
_width = newWidth;
|
||||
}
|
||||
|
||||
int GroupThumbs::height() const {
|
||||
return st::mediaviewGroupPadding.top()
|
||||
+ st::mediaviewGroupHeight
|
||||
+ st::mediaviewGroupPadding.bottom();
|
||||
}
|
||||
|
||||
bool GroupThumbs::hiding() const {
|
||||
return _items.empty();
|
||||
}
|
||||
|
||||
bool GroupThumbs::hidden() const {
|
||||
return hiding() && !_waitingForAnimationStart && !_animation.animating();
|
||||
}
|
||||
|
||||
void GroupThumbs::checkForAnimationStart() {
|
||||
if (_waitingForAnimationStart) {
|
||||
_waitingForAnimationStart = false;
|
||||
_animation.start([this] { update(); }, 0., 1., kThumbDuration);
|
||||
}
|
||||
}
|
||||
|
||||
void GroupThumbs::update() {
|
||||
if (_cache.empty()) {
|
||||
return;
|
||||
}
|
||||
_updateRequests.fire_copy(_updatedRect);
|
||||
}
|
||||
|
||||
void GroupThumbs::paint(
|
||||
Painter &p,
|
||||
int x,
|
||||
int y,
|
||||
int outerWidth,
|
||||
TimeMs ms) {
|
||||
const auto progress = _waitingForAnimationStart
|
||||
? 0.
|
||||
: _animation.current(ms, 1.);
|
||||
x += (_width / 2);
|
||||
y += st::mediaviewGroupPadding.top();
|
||||
for (auto i = _cache.begin(); i != _cache.end();) {
|
||||
const auto &thumb = i->second;
|
||||
thumb->paint(p, x, y, outerWidth, progress);
|
||||
if (thumb->removed()) {
|
||||
_dying.erase(
|
||||
ranges::remove(
|
||||
_dying,
|
||||
thumb.get(),
|
||||
[](not_null<Thumb*> thumb) { return thumb.get(); }),
|
||||
_dying.end());
|
||||
i = _cache.erase(i);
|
||||
} else {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ClickHandlerPtr GroupThumbs::getState(QPoint point) const {
|
||||
point -= QPoint((_width / 2), st::mediaviewGroupPadding.top());
|
||||
for (const auto &cacheItem : _cache) {
|
||||
const auto &thumb = cacheItem.second;
|
||||
if (auto link = thumb->getState(point)) {
|
||||
return link;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void GroupThumbs::countUpdatedRect() {
|
||||
if (_cache.empty()) {
|
||||
return;
|
||||
}
|
||||
auto min = _width;
|
||||
auto max = 0;
|
||||
const auto left = [](const auto &cacheItem) {
|
||||
const auto &[key, thumb] = cacheItem;
|
||||
return thumb->leftToUpdate();
|
||||
};
|
||||
const auto right = [](const auto &cacheItem) {
|
||||
const auto &[key, thumb] = cacheItem;
|
||||
return thumb->rightToUpdate();
|
||||
};
|
||||
accumulate_min(min, left(*ranges::max_element(
|
||||
_cache,
|
||||
std::greater<>(),
|
||||
left)));
|
||||
accumulate_max(max, right(*ranges::max_element(
|
||||
_cache,
|
||||
std::less<>(),
|
||||
right)));
|
||||
_updatedRect = QRect(
|
||||
min,
|
||||
st::mediaviewGroupPadding.top(),
|
||||
max - min,
|
||||
st::mediaviewGroupHeight);
|
||||
}
|
||||
|
||||
GroupThumbs::~GroupThumbs() = default;
|
||||
|
||||
} // namespace View
|
||||
} // namespace Media
|
||||
116
Telegram/SourceFiles/media/view/media_view_group_thumbs.h
Normal file
116
Telegram/SourceFiles/media/view/media_view_group_thumbs.h
Normal file
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
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-2017 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "history/history_item_components.h"
|
||||
#include "base/weak_ptr.h"
|
||||
|
||||
class SharedMediaWithLastSlice;
|
||||
class UserPhotosSlice;
|
||||
|
||||
namespace Media {
|
||||
namespace View {
|
||||
|
||||
class GroupThumbs : public base::has_weak_ptr {
|
||||
public:
|
||||
using Key = base::variant<PhotoId, FullMsgId>;
|
||||
|
||||
static void Refresh(
|
||||
std::unique_ptr<GroupThumbs> &instance,
|
||||
const SharedMediaWithLastSlice &slice,
|
||||
int index,
|
||||
int availableWidth);
|
||||
static void Refresh(
|
||||
std::unique_ptr<GroupThumbs> &instance,
|
||||
const UserPhotosSlice &slice,
|
||||
int index,
|
||||
int availableWidth);
|
||||
void clear();
|
||||
|
||||
void resizeToWidth(int newWidth);
|
||||
int height() const;
|
||||
bool hiding() const;
|
||||
bool hidden() const;
|
||||
void checkForAnimationStart();
|
||||
|
||||
void paint(Painter &p, int x, int y, int outerWidth, TimeMs ms);
|
||||
ClickHandlerPtr getState(QPoint point) const;
|
||||
|
||||
rpl::producer<QRect> updateRequests() const {
|
||||
return _updateRequests.events();
|
||||
}
|
||||
|
||||
rpl::producer<Key> activateRequests() const {
|
||||
return _activateStream.events();
|
||||
}
|
||||
|
||||
rpl::lifetime &lifetime() {
|
||||
return _lifetime;
|
||||
}
|
||||
|
||||
using Context = base::optional_variant<PeerId, MessageGroupId>;
|
||||
|
||||
GroupThumbs(Context context);
|
||||
~GroupThumbs();
|
||||
|
||||
private:
|
||||
class Thumb;
|
||||
|
||||
template <typename Slice>
|
||||
static void RefreshFromSlice(
|
||||
std::unique_ptr<GroupThumbs> &instance,
|
||||
const Slice &slice,
|
||||
int index,
|
||||
int availableWidth);
|
||||
template <typename Slice>
|
||||
void fillItems(const Slice &slice, int from, int index, int till);
|
||||
void updateContext(Context context);
|
||||
void markCacheStale();
|
||||
not_null<Thumb*> validateCacheEntry(Key key);
|
||||
std::unique_ptr<Thumb> createThumb(Key key);
|
||||
std::unique_ptr<Thumb> createThumb(Key key, ImagePtr image);
|
||||
|
||||
void update();
|
||||
void countUpdatedRect();
|
||||
void animateAliveItems(int current);
|
||||
void fillDyingItems(const std::vector<not_null<Thumb*>> &old);
|
||||
void markAsDying(not_null<Thumb*> thumb);
|
||||
void markRestAsDying();
|
||||
void animatePreviouslyAlive(const std::vector<not_null<Thumb*>> &old);
|
||||
void startDelayedAnimation();
|
||||
|
||||
Context _context;
|
||||
bool _waitingForAnimationStart = true;
|
||||
Animation _animation;
|
||||
std::vector<not_null<Thumb*>> _items;
|
||||
std::vector<not_null<Thumb*>> _dying;
|
||||
base::flat_map<Key, std::unique_ptr<Thumb>> _cache;
|
||||
int _width = 0;
|
||||
QRect _updatedRect;
|
||||
|
||||
rpl::event_stream<QRect> _updateRequests;
|
||||
rpl::event_stream<Key> _activateStream;
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
};
|
||||
|
||||
} // namespace View
|
||||
} // namespace Media
|
||||
@@ -196,6 +196,13 @@ mediaviewCaptionPadding: margins(18px, 10px, 18px, 10px);
|
||||
mediaviewCaptionMargin: size(11px, 11px);
|
||||
mediaviewCaptionRadius: 2px;
|
||||
|
||||
mediaviewGroupPadding: margins(0px, 14px, 0px, 14px);
|
||||
mediaviewGroupHeight: 80px;
|
||||
mediaviewGroupWidth: 56px;
|
||||
mediaviewGroupWidthMax: 160px;
|
||||
mediaviewGroupSkip: 3px;
|
||||
mediaviewGroupSkipCurrent: 12px;
|
||||
|
||||
themePreviewSize: size(903px, 584px);
|
||||
themePreviewBg: windowBg;
|
||||
themePreviewOverlayOpacity: 0.8;
|
||||
|
||||
@@ -27,41 +27,29 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "core/file_utilities.h"
|
||||
#include "ui/widgets/popup_menu.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "ui/text_options.h"
|
||||
#include "media/media_clip_reader.h"
|
||||
#include "media/view/media_clip_controller.h"
|
||||
#include "styles/style_mediaview.h"
|
||||
#include "styles/style_history.h"
|
||||
#include "media/view/media_view_group_thumbs.h"
|
||||
#include "media/media_audio.h"
|
||||
#include "history/history_message.h"
|
||||
#include "history/history_media_types.h"
|
||||
#include "window/themes/window_theme_preview.h"
|
||||
#include "window/window_peer_menu.h"
|
||||
#include "base/task_queue.h"
|
||||
#include "observer_peer.h"
|
||||
#include "auth_session.h"
|
||||
#include "messenger.h"
|
||||
#include "storage/file_download.h"
|
||||
#include "calls/calls_instance.h"
|
||||
#include "styles/style_mediaview.h"
|
||||
#include "styles/style_history.h"
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr auto kPreloadCount = 4;
|
||||
|
||||
TextParseOptions _captionTextOptions = {
|
||||
TextParseLinks | TextParseMentions | TextParseHashtags | TextParseMultiline | TextParseRichText, // flags
|
||||
0, // maxw
|
||||
0, // maxh
|
||||
Qt::LayoutDirectionAuto, // dir
|
||||
};
|
||||
TextParseOptions _captionBotOptions = {
|
||||
TextParseLinks | TextParseMentions | TextParseHashtags | TextParseMultiline | TextParseRichText | TextParseBotCommands, // flags
|
||||
0, // maxw
|
||||
0, // maxh
|
||||
Qt::LayoutDirectionAuto, // dir
|
||||
};
|
||||
|
||||
// Preload X message ids before and after current.
|
||||
constexpr auto kIdsLimit = 32;
|
||||
constexpr auto kIdsLimit = 48;
|
||||
|
||||
// Preload next messages if we went further from current than that.
|
||||
constexpr auto kIdsPreloadAfter = 28;
|
||||
@@ -84,7 +72,8 @@ struct MediaView::UserPhotos {
|
||||
rpl::lifetime lifetime;
|
||||
};
|
||||
|
||||
MediaView::MediaView() : TWidget(nullptr)
|
||||
MediaView::MediaView()
|
||||
: TWidget(nullptr)
|
||||
, _transparentBrush(style::transparentPlaceholderBrush())
|
||||
, _animStarted(getms())
|
||||
, _docDownload(this, lang(lng_media_download), st::mediaviewFileLink)
|
||||
@@ -99,7 +88,7 @@ MediaView::MediaView() : TWidget(nullptr)
|
||||
|
||||
TextCustomTagsMap custom;
|
||||
custom.insert(QChar('c'), qMakePair(textcmdStartLink(1), textcmdStopLink()));
|
||||
_saveMsgText.setRichText(st::mediaviewSaveMsgStyle, lang(lng_mediaview_saved), _textDlgOptions, custom);
|
||||
_saveMsgText.setRichText(st::mediaviewSaveMsgStyle, lang(lng_mediaview_saved), Ui::DialogTextOptions(), custom);
|
||||
_saveMsg = QRect(0, 0, _saveMsgText.maxWidth() + st::mediaviewSaveMsgPadding.left() + st::mediaviewSaveMsgPadding.right(), st::mediaviewSaveMsgStyle.font->height + st::mediaviewSaveMsgPadding.top() + st::mediaviewSaveMsgPadding.bottom());
|
||||
_saveMsgText.setLink(1, std::make_shared<LambdaClickHandler>([this] { showSaveMsgFile(); }));
|
||||
|
||||
@@ -211,12 +200,22 @@ bool MediaView::fileBubbleShown() const {
|
||||
bool MediaView::gifShown() const {
|
||||
if (_gif && _gif->ready()) {
|
||||
if (!_gif->started()) {
|
||||
if (_doc && (_doc->isVideoFile() || _doc->isVideoMessage()) && _autoplayVideoDocument != _doc && !_gif->videoPaused()) {
|
||||
_gif->pauseResumeVideo();
|
||||
const_cast<MediaView*>(this)->_videoPaused = _gif->videoPaused();
|
||||
const auto streamVideo = _doc
|
||||
&& (_doc->isVideoFile() || _doc->isVideoMessage());
|
||||
const auto pauseOnStart = (_autoplayVideoDocument != _doc);
|
||||
if (streamVideo && pauseOnStart && !_gif->videoPaused()) {
|
||||
const_cast<MediaView*>(this)->toggleVideoPaused();
|
||||
}
|
||||
auto rounding = (_doc && _doc->isVideoMessage()) ? ImageRoundRadius::Ellipse : ImageRoundRadius::None;
|
||||
_gif->start(_gif->width() / cIntRetinaFactor(), _gif->height() / cIntRetinaFactor(), _gif->width() / cIntRetinaFactor(), _gif->height() / cIntRetinaFactor(), rounding, RectPart::AllCorners);
|
||||
const auto rounding = (_doc && _doc->isVideoMessage())
|
||||
? ImageRoundRadius::Ellipse
|
||||
: ImageRoundRadius::None;
|
||||
_gif->start(
|
||||
_gif->width() / cIntRetinaFactor(),
|
||||
_gif->height() / cIntRetinaFactor(),
|
||||
_gif->width() / cIntRetinaFactor(),
|
||||
_gif->height() / cIntRetinaFactor(),
|
||||
rounding,
|
||||
RectPart::AllCorners);
|
||||
const_cast<MediaView*>(this)->_current = QPixmap();
|
||||
updateMixerVideoVolume();
|
||||
Global::RefVideoVolumeChanged().notify();
|
||||
@@ -351,7 +350,7 @@ void MediaView::updateControls() {
|
||||
_dateText = lng_mediaview_date_time(lt_date, d.date().toString(qsl("dd.MM.yy")), lt_time, d.time().toString(cTimeFormat()));
|
||||
}
|
||||
if (_from) {
|
||||
_fromName.setText(st::mediaviewTextStyle, (_from->migrateTo() ? _from->migrateTo() : _from)->name, _textNameOptions);
|
||||
_fromName.setText(st::mediaviewTextStyle, (_from->migrateTo() ? _from->migrateTo() : _from)->name, Ui::NameTextOptions());
|
||||
_nameNav = myrtlrect(st::mediaviewTextLeft, height() - st::mediaviewTextTop, qMin(_fromName.maxWidth(), width() / 3), st::mediaviewFont->height);
|
||||
_dateNav = myrtlrect(st::mediaviewTextLeft + _nameNav.width() + st::mediaviewTextSkip, height() - st::mediaviewTextTop, st::mediaviewFont->width(_dateText), st::mediaviewFont->height);
|
||||
} else {
|
||||
@@ -360,22 +359,65 @@ void MediaView::updateControls() {
|
||||
}
|
||||
updateHeader();
|
||||
refreshNavVisibility();
|
||||
resizeCenteredControls();
|
||||
|
||||
if (!_caption.isEmpty()) {
|
||||
int32 skipw = qMax(_dateNav.left() + _dateNav.width(), _headerNav.left() + _headerNav.width());
|
||||
int32 maxw = qMin(qMax(width() - 2 * skipw - st::mediaviewCaptionPadding.left() - st::mediaviewCaptionPadding.right() - 2 * st::mediaviewCaptionMargin.width(), int(st::msgMinWidth)), _caption.maxWidth());
|
||||
int32 maxh = qMin(_caption.countHeight(maxw), int(height() / 4 - st::mediaviewCaptionPadding.top() - st::mediaviewCaptionPadding.bottom() - 2 * st::mediaviewCaptionMargin.height()));
|
||||
_captionRect = QRect((width() - maxw) / 2, height() - maxh - st::mediaviewCaptionPadding.bottom() - st::mediaviewCaptionMargin.height(), maxw, maxh);
|
||||
} else {
|
||||
_captionRect = QRect();
|
||||
}
|
||||
if (_clipController) {
|
||||
setClipControllerGeometry();
|
||||
}
|
||||
updateOver(mapFromGlobal(QCursor::pos()));
|
||||
update();
|
||||
}
|
||||
|
||||
void MediaView::resizeCenteredControls() {
|
||||
const auto bottomSkip = std::max(
|
||||
_dateNav.left() + _dateNav.width(),
|
||||
_headerNav.left() + _headerNav.width())
|
||||
+ st::mediaviewCaptionMargin.width();
|
||||
_groupThumbsAvailableWidth = std::max(
|
||||
width() - 2 * bottomSkip,
|
||||
st::msgMinWidth
|
||||
+ st::mediaviewCaptionPadding.left()
|
||||
+ st::mediaviewCaptionPadding.right());
|
||||
_groupThumbsLeft = (width() - _groupThumbsAvailableWidth) / 2;
|
||||
refreshGroupThumbs();
|
||||
_groupThumbsTop = _groupThumbs ? (height() - _groupThumbs->height()) : 0;
|
||||
|
||||
refreshClipControllerGeometry();
|
||||
refreshCaptionGeometry();
|
||||
}
|
||||
|
||||
void MediaView::refreshCaptionGeometry() {
|
||||
if (_caption.isEmpty()) {
|
||||
_captionRect = QRect();
|
||||
return;
|
||||
}
|
||||
|
||||
if (_groupThumbs && _groupThumbs->hiding()) {
|
||||
_groupThumbs = nullptr;
|
||||
_groupThumbsRect = QRect();
|
||||
}
|
||||
const auto captionBottom = _clipController
|
||||
? (_clipController->y() - st::mediaviewCaptionMargin.height())
|
||||
: _groupThumbs
|
||||
? _groupThumbsTop
|
||||
: height() - st::mediaviewCaptionMargin.height();
|
||||
const auto captionWidth = std::min(
|
||||
_groupThumbsAvailableWidth
|
||||
- st::mediaviewCaptionPadding.left()
|
||||
- st::mediaviewCaptionPadding.right(),
|
||||
_caption.maxWidth());
|
||||
const auto captionHeight = std::min(
|
||||
_caption.countHeight(captionWidth),
|
||||
height() / 4
|
||||
- st::mediaviewCaptionPadding.top()
|
||||
- st::mediaviewCaptionPadding.bottom()
|
||||
- 2 * st::mediaviewCaptionMargin.height());
|
||||
_captionRect = QRect(
|
||||
(width() - captionWidth) / 2,
|
||||
captionBottom
|
||||
- captionHeight
|
||||
- st::mediaviewCaptionPadding.bottom(),
|
||||
captionWidth,
|
||||
captionHeight);
|
||||
}
|
||||
|
||||
void MediaView::updateActions() {
|
||||
_actions.clear();
|
||||
|
||||
@@ -413,13 +455,27 @@ void MediaView::updateActions() {
|
||||
}
|
||||
_actions.push_back({ lang(lng_mediaview_save_as), SLOT(onSaveAs()) });
|
||||
|
||||
if (auto overviewType =
|
||||
sharedMediaType()
|
||||
| SharedMediaOverviewType) {
|
||||
if (const auto overviewType = computeOverviewType()) {
|
||||
_actions.push_back({ lang(_doc ? lng_mediaview_files_all : lng_mediaview_photos_all), SLOT(onOverview()) });
|
||||
}
|
||||
}
|
||||
|
||||
auto MediaView::computeOverviewType() const
|
||||
-> base::optional<SharedMediaType> {
|
||||
if (const auto mediaType = sharedMediaType()) {
|
||||
if (const auto overviewType = SharedMediaOverviewType(*mediaType)) {
|
||||
return overviewType;
|
||||
} else if (mediaType == SharedMediaType::PhotoVideo) {
|
||||
if (_photo) {
|
||||
return SharedMediaOverviewType(SharedMediaType::Photo);
|
||||
} else if (_doc) {
|
||||
return SharedMediaOverviewType(SharedMediaType::Video);
|
||||
}
|
||||
}
|
||||
}
|
||||
return base::none;
|
||||
}
|
||||
|
||||
void MediaView::step_state(TimeMs ms, bool timer) {
|
||||
bool result = false;
|
||||
for (Showing::iterator i = _animations.begin(); i != _animations.end();) {
|
||||
@@ -454,7 +510,17 @@ void MediaView::step_state(TimeMs ms, bool timer) {
|
||||
} else {
|
||||
a_cOpacity.update(dt, anim::linear);
|
||||
}
|
||||
QRegion toUpdate = QRegion() + (_over == OverLeftNav ? _leftNav : _leftNavIcon) + (_over == OverRightNav ? _rightNav : _rightNavIcon) + (_over == OverClose ? _closeNav : _closeNavIcon) + _saveNavIcon + _moreNavIcon + _headerNav + _nameNav + _dateNav + _captionRect.marginsAdded(st::mediaviewCaptionPadding);
|
||||
const auto toUpdate = QRegion()
|
||||
+ (_over == OverLeftNav ? _leftNav : _leftNavIcon)
|
||||
+ (_over == OverRightNav ? _rightNav : _rightNavIcon)
|
||||
+ (_over == OverClose ? _closeNav : _closeNavIcon)
|
||||
+ _saveNavIcon
|
||||
+ _moreNavIcon
|
||||
+ _headerNav
|
||||
+ _nameNav
|
||||
+ _dateNav
|
||||
+ _captionRect.marginsAdded(st::mediaviewCaptionPadding)
|
||||
+ _groupThumbsRect;
|
||||
update(toUpdate);
|
||||
if (dt < 1) result = true;
|
||||
}
|
||||
@@ -464,7 +530,9 @@ void MediaView::step_state(TimeMs ms, bool timer) {
|
||||
}
|
||||
|
||||
void MediaView::updateCursor() {
|
||||
setCursor(_controlsState == ControlsHidden ? Qt::BlankCursor : (_over == OverNone ? style::cur_default : style::cur_pointer));
|
||||
setCursor(_controlsState == ControlsHidden
|
||||
? Qt::BlankCursor
|
||||
: (_over == OverNone ? style::cur_default : style::cur_pointer));
|
||||
}
|
||||
|
||||
float64 MediaView::radialProgress() const {
|
||||
@@ -512,21 +580,26 @@ void MediaView::step_radial(TimeMs ms, bool timer) {
|
||||
_radial.stop();
|
||||
return;
|
||||
}
|
||||
auto wasAnimating = _radial.animating();
|
||||
const auto wasAnimating = _radial.animating();
|
||||
_radial.update(radialProgress(), !radialLoading(), ms + radialTimeShift());
|
||||
if (timer && (wasAnimating || _radial.animating())) {
|
||||
update(radialRect());
|
||||
}
|
||||
if (_doc && _doc->loaded() && _doc->size < App::kImageSizeLimit && (!_radial.animating() || _doc->isAnimation() || _doc->isVideoFile())) {
|
||||
const auto ready = _doc && _doc->loaded();
|
||||
const auto streamVideo = ready && (_doc->isAnimation() || _doc->isVideoFile());
|
||||
const auto tryOpenImage = ready && (_doc->size < App::kImageSizeLimit);
|
||||
if (ready && ((tryOpenImage && !_radial.animating()) || streamVideo)) {
|
||||
if (_doc->isVideoFile() || _doc->isVideoMessage()) {
|
||||
_autoplayVideoDocument = _doc;
|
||||
}
|
||||
if (!_doc->data().isEmpty() && (_doc->isAnimation() || _doc->isVideoFile())) {
|
||||
if (!_doc->data().isEmpty() && streamVideo) {
|
||||
displayDocument(_doc, App::histItemById(_msgid));
|
||||
} else {
|
||||
auto &location = _doc->location(true);
|
||||
if (location.accessEnable()) {
|
||||
if (_doc->isAnimation() || _doc->isVideoFile() || _doc->isTheme() || QImageReader(location.name()).canRead()) {
|
||||
if (streamVideo
|
||||
|| _doc->isTheme()
|
||||
|| QImageReader(location.name()).canRead()) {
|
||||
displayDocument(_doc, App::histItemById(_msgid));
|
||||
}
|
||||
location.accessDisable();
|
||||
@@ -684,6 +757,7 @@ void MediaView::onHideControls(bool force) {
|
||||
}
|
||||
if (_controlsState == ControlsHiding || _controlsState == ControlsHidden) return;
|
||||
|
||||
_lastMouseMovePos = mapFromGlobal(QCursor::pos());
|
||||
_controlsState = ControlsHiding;
|
||||
_controlsAnimStarted = getms();
|
||||
a_cOpacity.start(0);
|
||||
@@ -963,9 +1037,7 @@ void MediaView::onDelete() {
|
||||
void MediaView::onOverview() {
|
||||
if (_menu) _menu->hideMenu(true);
|
||||
update();
|
||||
if (auto overviewType =
|
||||
sharedMediaType()
|
||||
| SharedMediaOverviewType) {
|
||||
if (const auto overviewType = computeOverviewType()) {
|
||||
close();
|
||||
SharedMediaShowOverview(*overviewType, _history);
|
||||
}
|
||||
@@ -991,14 +1063,14 @@ base::optional<MediaView::SharedMediaType> MediaView::sharedMediaType() const {
|
||||
if (auto item = App::histItemById(_msgid)) {
|
||||
if (_photo) {
|
||||
if (item->toHistoryMessage()) {
|
||||
return Type::Photo;
|
||||
return Type::PhotoVideo;
|
||||
}
|
||||
return Type::ChatPhoto;
|
||||
} else if (_doc) {
|
||||
if (_doc->isGifv()) {
|
||||
return Type::GIF;
|
||||
} else if (_doc->isVideoFile()) {
|
||||
return Type::Video;
|
||||
return Type::PhotoVideo;
|
||||
}
|
||||
return Type::File;
|
||||
}
|
||||
@@ -1171,6 +1243,85 @@ void MediaView::refreshMediaViewer() {
|
||||
preloadData(0);
|
||||
}
|
||||
|
||||
void MediaView::refreshCaption(HistoryItem *item) {
|
||||
_caption = Text();
|
||||
|
||||
const auto media = item ? item->getMedia() : nullptr;
|
||||
if (!media) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto caption = media->getCaption();
|
||||
if (caption.text.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
const auto asBot = [&] {
|
||||
if (const auto author = item->author()->asUser()) {
|
||||
return author->botInfo != nullptr;
|
||||
}
|
||||
return false;
|
||||
}();
|
||||
_caption = Text(st::msgMinWidth);
|
||||
_caption.setMarkedText(
|
||||
st::mediaviewCaptionStyle,
|
||||
caption,
|
||||
Ui::ItemTextOptions(item));
|
||||
}
|
||||
|
||||
void MediaView::refreshGroupThumbs() {
|
||||
const auto existed = (_groupThumbs != nullptr);
|
||||
if (_index && _sharedMediaData) {
|
||||
Media::View::GroupThumbs::Refresh(
|
||||
_groupThumbs,
|
||||
*_sharedMediaData,
|
||||
*_index,
|
||||
_groupThumbsAvailableWidth);
|
||||
} else if (_index && _userPhotosData) {
|
||||
Media::View::GroupThumbs::Refresh(
|
||||
_groupThumbs,
|
||||
*_userPhotosData,
|
||||
*_index,
|
||||
_groupThumbsAvailableWidth);
|
||||
} else if (_groupThumbs) {
|
||||
_groupThumbs->clear();
|
||||
_groupThumbs->resizeToWidth(_groupThumbsAvailableWidth);
|
||||
}
|
||||
if (_groupThumbs && !existed) {
|
||||
initGroupThumbs();
|
||||
}
|
||||
}
|
||||
|
||||
void MediaView::initGroupThumbs() {
|
||||
Expects(_groupThumbs != nullptr);
|
||||
|
||||
_groupThumbs->updateRequests(
|
||||
) | rpl::start_with_next([this](QRect rect) {
|
||||
const auto shift = (width() / 2);
|
||||
_groupThumbsRect = QRect(
|
||||
shift + rect.x(),
|
||||
_groupThumbsTop,
|
||||
rect.width(),
|
||||
_groupThumbs->height());
|
||||
update(_groupThumbsRect);
|
||||
}, _groupThumbs->lifetime());
|
||||
|
||||
_groupThumbs->activateRequests(
|
||||
) | rpl::start_with_next([this](Media::View::GroupThumbs::Key key) {
|
||||
if (const auto photoId = base::get_if<PhotoId>(&key)) {
|
||||
const auto photo = App::photo(*photoId);
|
||||
moveToEntity({ photo, nullptr });
|
||||
} else if (const auto itemId = base::get_if<FullMsgId>(&key)) {
|
||||
moveToEntity(entityForItemId(*itemId));
|
||||
}
|
||||
}, _groupThumbs->lifetime());
|
||||
|
||||
_groupThumbsRect = QRect(
|
||||
_groupThumbsLeft,
|
||||
_groupThumbsTop,
|
||||
width() - 2 * _groupThumbsLeft,
|
||||
height() - _groupThumbsTop);
|
||||
}
|
||||
|
||||
void MediaView::showPhoto(not_null<PhotoData*> photo, HistoryItem *context) {
|
||||
if (context) {
|
||||
setContext(context);
|
||||
@@ -1254,7 +1405,7 @@ void MediaView::showDocument(not_null<DocumentData*> document, HistoryItem *cont
|
||||
void MediaView::displayPhoto(not_null<PhotoData*> photo, HistoryItem *item) {
|
||||
stopGif();
|
||||
destroyThemePreview();
|
||||
_doc = nullptr;
|
||||
_doc = _autoplayVideoDocument = nullptr;
|
||||
_fullScreenVideo = false;
|
||||
_photo = photo;
|
||||
_radial.stop();
|
||||
@@ -1265,21 +1416,7 @@ void MediaView::displayPhoto(not_null<PhotoData*> photo, HistoryItem *item) {
|
||||
|
||||
_zoom = 0;
|
||||
|
||||
_caption = Text();
|
||||
if (const auto media = item ? item->getMedia() : nullptr) {
|
||||
const auto caption = media->getCaption();
|
||||
if (!caption.text.isEmpty()) {
|
||||
auto asBot = (item->author()->isUser()
|
||||
&& item->author()->asUser()->botInfo);
|
||||
auto skipw = qMax(_dateNav.left() + _dateNav.width(), _headerNav.left() + _headerNav.width());
|
||||
auto maxw = qMin(qMax(width() - 2 * skipw - st::mediaviewCaptionPadding.left() - st::mediaviewCaptionPadding.right() - 2 * st::mediaviewCaptionMargin.width(), int(st::msgMinWidth)), _caption.maxWidth());
|
||||
_caption = Text(maxw);
|
||||
_caption.setMarkedText(
|
||||
st::mediaviewCaptionStyle,
|
||||
caption,
|
||||
itemTextOptions(item));
|
||||
}
|
||||
}
|
||||
refreshCaption(item);
|
||||
|
||||
_zoomToScreen = 0;
|
||||
Auth().downloader().clearPriorities();
|
||||
@@ -1341,7 +1478,9 @@ void MediaView::displayDocument(DocumentData *doc, HistoryItem *item) { // empty
|
||||
_autoplayVideoDocument = nullptr;
|
||||
}
|
||||
|
||||
_caption = Text();
|
||||
if (documentChanged) {
|
||||
refreshCaption(item);
|
||||
}
|
||||
if (_doc) {
|
||||
if (_doc->sticker()) {
|
||||
_doc->checkSticker();
|
||||
@@ -1568,38 +1707,43 @@ void MediaView::initThemePreview() {
|
||||
auto &location = _doc->location();
|
||||
if (!location.isEmpty() && location.accessEnable()) {
|
||||
_themePreviewShown = true;
|
||||
auto path = _doc->location().name();
|
||||
auto id = _themePreviewId = rand_value<uint64>();
|
||||
auto ready = base::lambda_guarded(this, [this, id](std::unique_ptr<Window::Theme::Preview> result) {
|
||||
if (id != _themePreviewId) {
|
||||
return;
|
||||
}
|
||||
_themePreviewId = 0;
|
||||
_themePreview = std::move(result);
|
||||
if (_themePreview) {
|
||||
_themeApply.create(this, langFactory(lng_theme_preview_apply), st::themePreviewApplyButton);
|
||||
_themeApply->show();
|
||||
_themeApply->setClickedCallback([this] {
|
||||
auto preview = std::move(_themePreview);
|
||||
close();
|
||||
Window::Theme::Apply(std::move(preview));
|
||||
});
|
||||
_themeCancel.create(this, langFactory(lng_cancel), st::themePreviewCancelButton);
|
||||
_themeCancel->show();
|
||||
_themeCancel->setClickedCallback([this] { close(); });
|
||||
updateControls();
|
||||
}
|
||||
update();
|
||||
});
|
||||
|
||||
Window::Theme::CurrentData current;
|
||||
current.backgroundId = Window::Theme::Background()->id();
|
||||
current.backgroundImage = Window::Theme::Background()->pixmap();
|
||||
current.backgroundTiled = Window::Theme::Background()->tile();
|
||||
|
||||
const auto path = _doc->location().name();
|
||||
const auto id = _themePreviewId = rand_value<uint64>();
|
||||
const auto weak = make_weak(this);
|
||||
crl::async([=] {
|
||||
auto preview = Window::Theme::GeneratePreview(path, current);
|
||||
crl::on_main([ready, result = std::move(preview)]() mutable {
|
||||
ready(std::move(result));
|
||||
crl::on_main(weak, [=, result = std::move(preview)]() mutable {
|
||||
if (id != _themePreviewId) {
|
||||
return;
|
||||
}
|
||||
_themePreviewId = 0;
|
||||
_themePreview = std::move(result);
|
||||
if (_themePreview) {
|
||||
_themeApply.create(
|
||||
this,
|
||||
langFactory(lng_theme_preview_apply),
|
||||
st::themePreviewApplyButton);
|
||||
_themeApply->show();
|
||||
_themeApply->setClickedCallback([this] {
|
||||
auto preview = std::move(_themePreview);
|
||||
close();
|
||||
Window::Theme::Apply(std::move(preview));
|
||||
});
|
||||
_themeCancel.create(
|
||||
this,
|
||||
langFactory(lng_cancel),
|
||||
st::themePreviewCancelButton);
|
||||
_themeCancel->show();
|
||||
_themeCancel->setClickedCallback([this] { close(); });
|
||||
updateControls();
|
||||
}
|
||||
update();
|
||||
});
|
||||
});
|
||||
location.accessDisable();
|
||||
@@ -1611,7 +1755,7 @@ void MediaView::createClipController() {
|
||||
if (!_doc->isVideoFile() && !_doc->isVideoMessage()) return;
|
||||
|
||||
_clipController.create(this);
|
||||
setClipControllerGeometry();
|
||||
refreshClipControllerGeometry();
|
||||
_clipController->show();
|
||||
|
||||
connect(_clipController, SIGNAL(playPressed()), this, SLOT(onVideoPauseResume()));
|
||||
@@ -1625,10 +1769,18 @@ void MediaView::createClipController() {
|
||||
connect(Media::Player::mixer(), SIGNAL(updated(const AudioMsgId&)), this, SLOT(onVideoPlayProgress(const AudioMsgId&)));
|
||||
}
|
||||
|
||||
void MediaView::setClipControllerGeometry() {
|
||||
Assert(_clipController != nullptr);
|
||||
void MediaView::refreshClipControllerGeometry() {
|
||||
if (!_clipController) {
|
||||
return;
|
||||
}
|
||||
|
||||
int controllerBottom = _captionRect.isEmpty() ? height() : _captionRect.y();
|
||||
if (_groupThumbs && _groupThumbs->hiding()) {
|
||||
_groupThumbs = nullptr;
|
||||
_groupThumbsRect = QRect();
|
||||
}
|
||||
const auto controllerBottom = _groupThumbs
|
||||
? _groupThumbsTop
|
||||
: height();
|
||||
_clipController->setGeometry(
|
||||
(width() - _clipController->width()) / 2,
|
||||
controllerBottom - _clipController->height() - st::mediaviewCaptionPadding.bottom() - st::mediaviewCaptionMargin.height(),
|
||||
@@ -1646,11 +1798,7 @@ void MediaView::onVideoPauseResume() {
|
||||
} else if (_gif->state() == Media::Clip::State::Finished) {
|
||||
restartVideoAtSeekPosition(0);
|
||||
} else {
|
||||
_gif->pauseResumeVideo();
|
||||
_videoPaused = _gif->videoPaused();
|
||||
if (_videoIsSilent) {
|
||||
updateSilentVideoPlaybackState();
|
||||
}
|
||||
toggleVideoPaused();
|
||||
}
|
||||
} else {
|
||||
stopGif();
|
||||
@@ -1659,6 +1807,14 @@ void MediaView::onVideoPauseResume() {
|
||||
}
|
||||
}
|
||||
|
||||
void MediaView::toggleVideoPaused() {
|
||||
_gif->pauseResumeVideo();
|
||||
_videoPaused = _gif->videoPaused();
|
||||
if (_videoIsSilent) {
|
||||
updateSilentVideoPlaybackState();
|
||||
}
|
||||
}
|
||||
|
||||
void MediaView::restartVideoAtSeekPosition(TimeMs positionMs) {
|
||||
_autoplayVideoDocument = _doc;
|
||||
|
||||
@@ -2045,6 +2201,27 @@ void MediaView::paintEvent(QPaintEvent *e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_groupThumbs && _groupThumbsRect.intersects(r)) {
|
||||
p.setOpacity(co);
|
||||
_groupThumbs->paint(
|
||||
p,
|
||||
_groupThumbsLeft,
|
||||
_groupThumbsTop,
|
||||
width(),
|
||||
ms);
|
||||
if (_groupThumbs->hidden()) {
|
||||
_groupThumbs = nullptr;
|
||||
_groupThumbsRect = QRect();
|
||||
}
|
||||
}
|
||||
}
|
||||
checkGroupThumbsAnimation();
|
||||
}
|
||||
|
||||
void MediaView::checkGroupThumbsAnimation() {
|
||||
if (_groupThumbs && (!_gif || _gif->started())) {
|
||||
_groupThumbs->checkForAnimationStart();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2157,8 +2334,14 @@ void MediaView::keyPressEvent(QKeyEvent *e) {
|
||||
onVideoPauseResume();
|
||||
}
|
||||
} else if (e->key() == Qt::Key_Left) {
|
||||
if (_controlsHideTimer.isActive()) {
|
||||
activateControls();
|
||||
}
|
||||
moveToNext(-1);
|
||||
} else if (e->key() == Qt::Key_Right) {
|
||||
if (_controlsHideTimer.isActive()) {
|
||||
activateControls();
|
||||
}
|
||||
moveToNext(1);
|
||||
} else if (e->modifiers().testFlag(Qt::ControlModifier) && (e->key() == Qt::Key_Plus || e->key() == Qt::Key_Equal || e->key() == ']' || e->key() == Qt::Key_Asterisk || e->key() == Qt::Key_Minus || e->key() == Qt::Key_Underscore || e->key() == Qt::Key_0)) {
|
||||
if (e->key() == Qt::Key_Plus || e->key() == Qt::Key_Equal || e->key() == Qt::Key_Asterisk || e->key() == ']') {
|
||||
@@ -2259,16 +2442,21 @@ MediaView::Entity MediaView::entityForSharedMedia(int index) const {
|
||||
// Last peer photo.
|
||||
return { *photo, nullptr };
|
||||
} else if (const auto itemId = base::get_if<FullMsgId>(&value)) {
|
||||
if (const auto item = App::histItemById(*itemId)) {
|
||||
if (const auto media = item->getMedia()) {
|
||||
if (const auto photo = media->getPhoto()) {
|
||||
return { photo, item };
|
||||
} else if (const auto document = media->getDocument()) {
|
||||
return { document, item };
|
||||
}
|
||||
return entityForItemId(*itemId);
|
||||
}
|
||||
return { base::none, nullptr };
|
||||
}
|
||||
|
||||
MediaView::Entity MediaView::entityForItemId(const FullMsgId &itemId) const {
|
||||
if (const auto item = App::histItemById(itemId)) {
|
||||
if (const auto media = item->getMedia()) {
|
||||
if (const auto photo = media->getPhoto()) {
|
||||
return { photo, item };
|
||||
} else if (const auto document = media->getDocument()) {
|
||||
return { document, item };
|
||||
}
|
||||
return { base::none, item };
|
||||
}
|
||||
return { base::none, item };
|
||||
}
|
||||
return { base::none, nullptr };
|
||||
}
|
||||
@@ -2319,12 +2507,14 @@ bool MediaView::moveToNext(int delta) {
|
||||
return false;
|
||||
}
|
||||
auto newIndex = *_index + delta;
|
||||
auto entity = entityByIndex(newIndex);
|
||||
return moveToEntity(entityByIndex(newIndex));
|
||||
}
|
||||
|
||||
bool MediaView::moveToEntity(const Entity &entity, int preloadDelta) {
|
||||
if (!entity.data && !entity.item) {
|
||||
return false;
|
||||
}
|
||||
_index = newIndex;
|
||||
if (auto item = entity.item) {
|
||||
if (const auto item = entity.item) {
|
||||
setContext(item);
|
||||
} else if (_peer) {
|
||||
setContext(_peer);
|
||||
@@ -2339,7 +2529,7 @@ bool MediaView::moveToNext(int delta) {
|
||||
} else {
|
||||
displayDocument(nullptr, entity.item);
|
||||
}
|
||||
preloadData(delta);
|
||||
preloadData(preloadDelta);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -2533,8 +2723,13 @@ void MediaView::updateOver(QPoint pos) {
|
||||
auto textState = _caption.getState(pos - _captionRect.topLeft(), _captionRect.width());
|
||||
lnk = textState.link;
|
||||
lnkhost = this;
|
||||
} else if (_groupThumbs && _groupThumbsRect.contains(pos)) {
|
||||
const auto point = pos - QPoint(_groupThumbsLeft, _groupThumbsTop);
|
||||
lnk = _groupThumbs->getState(point);
|
||||
lnkhost = this;
|
||||
}
|
||||
|
||||
|
||||
// retina
|
||||
if (pos.x() == width()) {
|
||||
pos.setX(pos.x() - 1);
|
||||
@@ -2629,7 +2824,9 @@ void MediaView::mouseReleaseEvent(QMouseEvent *e) {
|
||||
_pressed = false;
|
||||
}
|
||||
_down = OverNone;
|
||||
activateControls();
|
||||
if (!isHidden()) {
|
||||
activateControls();
|
||||
}
|
||||
}
|
||||
|
||||
void MediaView::contextMenuEvent(QContextMenuEvent *e) {
|
||||
@@ -2738,10 +2935,13 @@ bool MediaView::eventFilter(QObject *obj, QEvent *e) {
|
||||
auto type = e->type();
|
||||
if ((type == QEvent::MouseMove || type == QEvent::MouseButtonPress || type == QEvent::MouseButtonRelease) && obj->isWidgetType()) {
|
||||
if (isAncestorOf(static_cast<QWidget*>(obj))) {
|
||||
auto mouseEvent = static_cast<QMouseEvent*>(e);
|
||||
auto mousePosition = mapFromGlobal(mouseEvent->globalPos());
|
||||
bool activate = (mousePosition != _lastMouseMovePos);
|
||||
_lastMouseMovePos = mousePosition;
|
||||
const auto mouseEvent = static_cast<QMouseEvent*>(e);
|
||||
const auto mousePosition = mapFromGlobal(mouseEvent->globalPos());
|
||||
const auto delta = (mousePosition - _lastMouseMovePos);
|
||||
auto activate = delta.manhattanLength() >= st::mediaviewDeltaFromLastAction;
|
||||
if (activate) {
|
||||
_lastMouseMovePos = mousePosition;
|
||||
}
|
||||
if (type == QEvent::MouseButtonPress) {
|
||||
_mousePressed = true;
|
||||
activate = true;
|
||||
@@ -2749,7 +2949,12 @@ bool MediaView::eventFilter(QObject *obj, QEvent *e) {
|
||||
_mousePressed = false;
|
||||
activate = true;
|
||||
}
|
||||
if (activate) activateControls();
|
||||
if (activate) {
|
||||
if (_controlsState == ControlsHiding || _controlsState == ControlsHidden) {
|
||||
int a = 0;
|
||||
}
|
||||
activateControls();
|
||||
}
|
||||
}
|
||||
}
|
||||
return TWidget::eventFilter(obj, e);
|
||||
@@ -2766,6 +2971,8 @@ void MediaView::setVisible(bool visible) {
|
||||
_controlsHideTimer.stop();
|
||||
_controlsState = ControlsShown;
|
||||
a_cOpacity = anim::value(1, 1);
|
||||
_groupThumbs = nullptr;
|
||||
_groupThumbsRect = QRect();
|
||||
}
|
||||
TWidget::setVisible(visible);
|
||||
if (visible) {
|
||||
@@ -2857,9 +3064,7 @@ void MediaView::updateHeader() {
|
||||
_headerText = lang(lng_mediaview_single_photo);
|
||||
}
|
||||
}
|
||||
_headerHasLink = (
|
||||
sharedMediaType()
|
||||
| SharedMediaOverviewType) != base::none;
|
||||
_headerHasLink = computeOverviewType() != base::none;
|
||||
auto hwidth = st::mediaviewThickFont->width(_headerText);
|
||||
if (hwidth > width() / 3) {
|
||||
hwidth = width() / 3;
|
||||
|
||||
@@ -32,6 +32,9 @@ struct TrackState;
|
||||
namespace Clip {
|
||||
class Controller;
|
||||
} // namespace Clip
|
||||
namespace View {
|
||||
class GroupThumbs;
|
||||
} // namespace View
|
||||
} // namespace Media
|
||||
|
||||
namespace Ui {
|
||||
@@ -50,7 +53,7 @@ namespace Notify {
|
||||
struct PeerUpdate;
|
||||
} // namespace Notify
|
||||
|
||||
class MediaView : public TWidget, private base::Subscriber, public RPCSender, public ClickHandlerHost {
|
||||
class MediaView : public TWidget, private base::Subscriber, public ClickHandlerHost {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
@@ -158,6 +161,8 @@ private:
|
||||
Entity entityForUserPhotos(int index) const;
|
||||
Entity entityForSharedMedia(int index) const;
|
||||
Entity entityByIndex(int index) const;
|
||||
Entity entityForItemId(const FullMsgId &itemId) const;
|
||||
bool moveToEntity(const Entity &entity, int preloadDelta = 0);
|
||||
void setContext(base::optional_variant<
|
||||
not_null<HistoryItem*>,
|
||||
not_null<PeerData*>> context);
|
||||
@@ -171,6 +176,7 @@ private:
|
||||
using SharedMediaKey = SharedMediaWithLastSlice::Key;
|
||||
base::optional<SharedMediaType> sharedMediaType() const;
|
||||
base::optional<SharedMediaKey> sharedMediaKey() const;
|
||||
base::optional<SharedMediaType> computeOverviewType() const;
|
||||
bool validSharedMedia() const;
|
||||
void validateSharedMedia();
|
||||
void handleSharedMediaUpdate(SharedMediaWithLastSlice &&update);
|
||||
@@ -182,13 +188,16 @@ private:
|
||||
void validateUserPhotos();
|
||||
void handleUserPhotosUpdate(UserPhotosSlice &&update);
|
||||
|
||||
void refreshCaption(HistoryItem *item);
|
||||
void refreshMediaViewer();
|
||||
void refreshNavVisibility();
|
||||
void refreshGroupThumbs();
|
||||
|
||||
void dropdownHidden();
|
||||
void updateDocSize();
|
||||
void updateControls();
|
||||
void updateActions();
|
||||
void resizeCenteredControls();
|
||||
|
||||
void displayPhoto(not_null<PhotoData*> photo, HistoryItem *item);
|
||||
void displayDocument(DocumentData *document, HistoryItem *item);
|
||||
@@ -201,9 +210,11 @@ private:
|
||||
void updateVideoPlaybackState(const Media::Player::TrackState &state);
|
||||
void updateSilentVideoPlaybackState();
|
||||
void restartVideoAtSeekPosition(TimeMs positionMs);
|
||||
void toggleVideoPaused();
|
||||
|
||||
void createClipController();
|
||||
void setClipControllerGeometry();
|
||||
void refreshClipControllerGeometry();
|
||||
void refreshCaptionGeometry();
|
||||
|
||||
void initAnimation();
|
||||
void createClipReader();
|
||||
@@ -223,9 +234,6 @@ private:
|
||||
void radialStart();
|
||||
TimeMs radialTimeShift() const;
|
||||
|
||||
void deletePhotosDone(const MTPVector<MTPlong> &result);
|
||||
bool deletePhotosFail(const RPCError &error);
|
||||
|
||||
void updateHeader();
|
||||
void snapXY();
|
||||
|
||||
@@ -244,6 +252,9 @@ private:
|
||||
bool updateOverState(OverState newState);
|
||||
float64 overLevel(OverState control) const;
|
||||
|
||||
void checkGroupThumbsAnimation();
|
||||
void initGroupThumbs();
|
||||
|
||||
QBrush _transparentBrush;
|
||||
|
||||
PhotoData *_photo = nullptr;
|
||||
@@ -270,6 +281,11 @@ private:
|
||||
bool _fullScreenVideo = false;
|
||||
int _fullScreenZoomCache = 0;
|
||||
|
||||
std::unique_ptr<Media::View::GroupThumbs> _groupThumbs;
|
||||
QRect _groupThumbsRect;
|
||||
int _groupThumbsAvailableWidth = 0;
|
||||
int _groupThumbsLeft = 0;
|
||||
int _groupThumbsTop = 0;
|
||||
Text _caption;
|
||||
QRect _captionRect;
|
||||
|
||||
|
||||
@@ -47,6 +47,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "window/themes/window_theme.h"
|
||||
#include "history/history_location_manager.h"
|
||||
#include "ui/widgets/tooltip.h"
|
||||
#include "ui/text_options.h"
|
||||
#include "storage/serialize_common.h"
|
||||
#include "window/window_controller.h"
|
||||
#include "base/qthelp_regex.h"
|
||||
@@ -115,7 +116,7 @@ Messenger::Messenger(not_null<Core::Launcher*> launcher)
|
||||
|
||||
style::startManager();
|
||||
anim::startManager();
|
||||
HistoryInit();
|
||||
Ui::InitTextOptions();
|
||||
Media::Player::start();
|
||||
|
||||
DEBUG_LOG(("Application Info: inited..."));
|
||||
@@ -986,7 +987,6 @@ void Messenger::checkMediaViewActivation() {
|
||||
void Messenger::loggedOut() {
|
||||
if (_mediaView) {
|
||||
hideMediaView();
|
||||
_mediaView->rpcClear();
|
||||
_mediaView->clearData();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,14 +79,14 @@ overviewFileExtTop: 24px;
|
||||
overviewFileExtFg: windowFgActive;
|
||||
overviewFileExtFont: font(18px semibold);
|
||||
|
||||
overviewSongPause: icon {{ "playlist_pause", msgInBg }};
|
||||
overviewSongPauseSelected: icon {{ "playlist_pause", msgInBgSelected }};
|
||||
overviewSongPlay: icon {{ "playlist_play", msgInBg }};
|
||||
overviewSongPlaySelected: icon {{ "playlist_play", msgInBgSelected }};
|
||||
overviewSongCancel: icon {{ "playlist_cancel", msgInBg }};
|
||||
overviewSongCancelSelected: icon {{ "playlist_cancel", msgInBgSelected }};
|
||||
overviewSongDownload: icon {{ "playlist_download", msgInBg }};
|
||||
overviewSongDownloadSelected: icon {{ "playlist_download", msgInBgSelected }};
|
||||
overviewSongPause: icon {{ "playlist_pause", historyFileInIconFg }};
|
||||
overviewSongPauseSelected: icon {{ "playlist_pause", historyFileInIconFgSelected }};
|
||||
overviewSongPlay: icon {{ "playlist_play", historyFileInIconFg }};
|
||||
overviewSongPlaySelected: icon {{ "playlist_play", historyFileInIconFgSelected }};
|
||||
overviewSongCancel: icon {{ "playlist_cancel", historyFileInIconFg }};
|
||||
overviewSongCancelSelected: icon {{ "playlist_cancel", historyFileInIconFgSelected }};
|
||||
overviewSongDownload: icon {{ "playlist_download", historyFileInIconFg }};
|
||||
overviewSongDownloadSelected: icon {{ "playlist_download", historyFileInIconFgSelected }};
|
||||
overviewFileLayout: OverviewFileLayout {
|
||||
maxWidth: 520px;
|
||||
songPadding: margins(17px, 7px, 10px, 6px);
|
||||
|
||||
@@ -37,6 +37,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "history/history_media_types.h"
|
||||
#include "history/history_item_components.h"
|
||||
#include "ui/effects/round_checkbox.h"
|
||||
#include "ui/text_options.h"
|
||||
|
||||
namespace Overview {
|
||||
namespace Layout {
|
||||
@@ -619,7 +620,7 @@ void Voice::paint(Painter &p, const QRect &clip, TextSelection selection, const
|
||||
|
||||
if (radial) {
|
||||
QRect rinner(inner.marginsRemoved(QMargins(st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine)));
|
||||
auto &bg = selected ? st::msgInBgSelected : st::msgInBg;
|
||||
auto &bg = selected ? st::historyFileInRadialFgSelected : st::historyFileInRadialFg;
|
||||
_radial->draw(p, rinner, st::msgFileRadialLine, bg);
|
||||
}
|
||||
|
||||
@@ -754,12 +755,12 @@ void Voice::updateName() {
|
||||
auto version = 0;
|
||||
if (const auto forwarded = parent()->Get<HistoryMessageForwarded>()) {
|
||||
if (parent()->fromOriginal()->isChannel()) {
|
||||
_name.setText(st::semiboldTextStyle, lng_forwarded_channel(lt_channel, App::peerName(parent()->fromOriginal())), _textNameOptions);
|
||||
_name.setText(st::semiboldTextStyle, lng_forwarded_channel(lt_channel, App::peerName(parent()->fromOriginal())), Ui::NameTextOptions());
|
||||
} else {
|
||||
_name.setText(st::semiboldTextStyle, lng_forwarded(lt_user, App::peerName(parent()->fromOriginal())), _textNameOptions);
|
||||
_name.setText(st::semiboldTextStyle, lng_forwarded(lt_user, App::peerName(parent()->fromOriginal())), Ui::NameTextOptions());
|
||||
}
|
||||
} else {
|
||||
_name.setText(st::semiboldTextStyle, App::peerName(parent()->from()), _textNameOptions);
|
||||
_name.setText(st::semiboldTextStyle, App::peerName(parent()->from()), Ui::NameTextOptions());
|
||||
}
|
||||
version = parent()->fromOriginal()->nameVersion;
|
||||
_nameVersion = version;
|
||||
@@ -878,7 +879,7 @@ void Document::paint(Painter &p, const QRect &clip, TextSelection selection, con
|
||||
|
||||
if (radial) {
|
||||
auto rinner = inner.marginsRemoved(QMargins(st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine, st::msgFileRadialLine));
|
||||
auto &bg = selected ? st::msgInBgSelected : st::msgInBg;
|
||||
auto &bg = selected ? st::historyFileInRadialFgSelected : st::historyFileInRadialFg;
|
||||
_radial->draw(p, rinner, st::msgFileRadialLine, bg);
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,6 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "platform/linux/linux_libnotify.h"
|
||||
#include "platform/linux/linux_libs.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "base/task_queue.h"
|
||||
|
||||
namespace Platform {
|
||||
namespace Notifications {
|
||||
@@ -203,10 +202,9 @@ private:
|
||||
MsgId msgId = 0;
|
||||
};
|
||||
static void performOnMainQueue(NotificationDataStruct *data, base::lambda_once<void(Manager *manager)> task) {
|
||||
base::TaskQueue::Main().Put([weak = data->weak, task = std::move(task)]() mutable {
|
||||
if (auto strong = weak.lock()) {
|
||||
task(*strong);
|
||||
}
|
||||
const auto weak = data->weak;
|
||||
crl::on_main(weak, [=, task = std::move(task)]() mutable {
|
||||
task(*weak.lock());
|
||||
});
|
||||
}
|
||||
static void notificationDataFree(gpointer data) {
|
||||
|
||||
@@ -24,7 +24,6 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "platform/mac/mac_utilities.h"
|
||||
#include "styles/style_window.h"
|
||||
#include "mainwindow.h"
|
||||
#include "base/task_queue.h"
|
||||
#include "base/variant.h"
|
||||
|
||||
#include <thread>
|
||||
@@ -87,7 +86,7 @@ NSImage *qt_mac_create_nsimage(const QPixmap &pm);
|
||||
auto notificationManagerId = managerIdObject ? [managerIdObject unsignedLongLongValue] : 0ULL;
|
||||
DEBUG_LOG(("Received notification with instance %1, mine: %2").arg(notificationManagerId).arg(_managerId));
|
||||
if (notificationManagerId != _managerId) { // other app instance notification
|
||||
base::TaskQueue::Main().Put([] {
|
||||
crl::on_main([] {
|
||||
// Usually we show and activate main window when the application
|
||||
// is activated (receives applicationDidBecomeActive: notification).
|
||||
//
|
||||
@@ -112,17 +111,15 @@ NSImage *qt_mac_create_nsimage(const QPixmap &pm);
|
||||
NSNumber *msgObject = [notificationUserInfo objectForKey:@"msgid"];
|
||||
auto notificationMsgId = msgObject ? [msgObject intValue] : 0;
|
||||
if (notification.activationType == NSUserNotificationActivationTypeReplied) {
|
||||
auto notificationReply = QString::fromUtf8([[[notification response] string] UTF8String]);
|
||||
base::TaskQueue::Main().Put([manager = _manager, notificationPeerId, notificationMsgId, notificationReply] {
|
||||
if (manager) {
|
||||
manager->notificationReplied(notificationPeerId, notificationMsgId, notificationReply);
|
||||
}
|
||||
const auto notificationReply = QString::fromUtf8([[[notification response] string] UTF8String]);
|
||||
const auto manager = _manager;
|
||||
crl::on_main(manager, [=] {
|
||||
manager->notificationReplied(notificationPeerId, notificationMsgId, notificationReply);
|
||||
});
|
||||
} else if (notification.activationType == NSUserNotificationActivationTypeContentsClicked) {
|
||||
base::TaskQueue::Main().Put([manager = _manager, notificationPeerId, notificationMsgId] {
|
||||
if (manager) {
|
||||
manager->notificationActivated(notificationPeerId, notificationMsgId);
|
||||
}
|
||||
const auto manager = _manager;
|
||||
crl::on_main(manager, [=] {
|
||||
manager->notificationActivated(notificationPeerId, notificationMsgId);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -214,9 +211,9 @@ Manager::Private::Private(Manager *manager)
|
||||
subscribe(Global::RefWorkMode(), [this](DBIWorkMode mode) {
|
||||
// We need to update the delegate _after_ the tray icon change was done in Qt.
|
||||
// Because Qt resets the delegate.
|
||||
base::TaskQueue::Main().Put(base::lambda_guarded(this, [this] {
|
||||
crl::on_main(this, [=] {
|
||||
updateDelegate();
|
||||
}));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,6 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "platform/win/windows_event_filter.h"
|
||||
#include "platform/win/windows_dlls.h"
|
||||
#include "mainwindow.h"
|
||||
#include "base/task_queue.h"
|
||||
|
||||
#include <Shobjidl.h>
|
||||
#include <shellapi.h>
|
||||
@@ -226,10 +225,9 @@ public:
|
||||
~ToastEventHandler() = default;
|
||||
|
||||
void performOnMainQueue(base::lambda_once<void(Manager *manager)> task) {
|
||||
base::TaskQueue::Main().Put([weak = _weak, task = std::move(task)]() mutable {
|
||||
if (auto strong = weak.lock()) {
|
||||
task(*strong);
|
||||
}
|
||||
const auto weak = _weak;
|
||||
crl::on_main(weak, [=, task = std::move(task)]() mutable {
|
||||
task(*weak.lock());
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,6 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "history/history_location_manager.h"
|
||||
#include "storage/localstorage.h"
|
||||
#include "passcodewidget.h"
|
||||
#include "base/task_queue.h"
|
||||
#include "core/crash_reports.h"
|
||||
|
||||
#include <Shobjidl.h>
|
||||
|
||||
@@ -22,6 +22,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
|
||||
#include "ui/effects/ripple_animation.h"
|
||||
#include "ui/widgets/popup_menu.h"
|
||||
#include "ui/text_options.h"
|
||||
#include "styles/style_profile.h"
|
||||
#include "styles/style_widgets.h"
|
||||
#include "auth_session.h"
|
||||
@@ -100,7 +101,10 @@ void PeerListWidget::paintItem(Painter &p, int x, int y, Item *item, bool select
|
||||
item->peer->paintUserpicLeft(p, x + _st.photoPosition.x(), y + _st.photoPosition.y(), width(), _st.photoSize);
|
||||
|
||||
if (item->name.isEmpty()) {
|
||||
item->name.setText(st::msgNameStyle, App::peerName(item->peer), _textNameOptions);
|
||||
item->name.setText(
|
||||
st::msgNameStyle,
|
||||
App::peerName(item->peer),
|
||||
Ui::NameTextOptions());
|
||||
}
|
||||
int nameLeft = x + _st.namePosition.x();
|
||||
int nameTop = y + _st.namePosition.y();
|
||||
|
||||
@@ -872,10 +872,7 @@ void ParticipantsBoxController::kickMemberSure(not_null<UserData*> user) {
|
||||
}
|
||||
|
||||
void ParticipantsBoxController::removeAdmin(not_null<UserData*> user) {
|
||||
const auto phrase = _channel->isMegagroup()
|
||||
? lng_profile_sure_remove_admin
|
||||
: lng_profile_sure_remove_admin_channel;
|
||||
const auto text = phrase(lt_user, user->firstName);
|
||||
const auto text = lng_profile_sure_remove_admin(lt_user, user->firstName);
|
||||
const auto weak = base::make_weak(this);
|
||||
_editBox = Ui::show(Box<ConfirmBox>(text, lang(lng_box_remove), [=] {
|
||||
if (const auto strong = weak.get()) {
|
||||
@@ -1379,7 +1376,10 @@ void AddParticipantBoxController::showAdmin(not_null<UserData*> user, bool sure)
|
||||
// The user is not in the group yet.
|
||||
if (_channel->canAddMembers()) {
|
||||
if (!sure) {
|
||||
_editBox = Ui::show(Box<ConfirmBox>(lang(lng_sure_add_admin_invite), [weak, user] {
|
||||
const auto text = lang(_channel->isMegagroup()
|
||||
? lng_sure_add_admin_invite
|
||||
: lng_sure_add_admin_invite_channel);
|
||||
_editBox = Ui::show(Box<ConfirmBox>(text, [weak, user] {
|
||||
if (weak) {
|
||||
weak->showAdmin(user, true);
|
||||
}
|
||||
|
||||
@@ -476,7 +476,7 @@ void Uploader::partLoaded(const MTPBool &result, mtpRequestId requestId) {
|
||||
if (document->uploading()) {
|
||||
const auto doneParts = file.docSentParts
|
||||
- int(docRequestsSent.size());
|
||||
document->uploadingData->offset = std::max(
|
||||
document->uploadingData->offset = std::min(
|
||||
document->uploadingData->size,
|
||||
doneParts * file.docPartSize);
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "storage/storage_media_prepare.h"
|
||||
|
||||
#include "platform/platform_file_utilities.h"
|
||||
#include "base/task_queue.h"
|
||||
#include "storage/localimageloader.h"
|
||||
|
||||
namespace Storage {
|
||||
@@ -58,8 +57,8 @@ bool PrepareAlbumMediaIsWaiting(
|
||||
QSemaphore &semaphore,
|
||||
PreparedFile &file,
|
||||
int previewWidth) {
|
||||
// Use some special thread queue, like a separate QThreadPool.
|
||||
base::TaskQueue::Normal().Put([&, previewWidth] {
|
||||
// TODO: Use some special thread queue, like a separate QThreadPool.
|
||||
crl::async([=, &semaphore, &file] {
|
||||
const auto guard = gsl::finally([&] { semaphore.release(); });
|
||||
if (!file.path.isEmpty()) {
|
||||
file.mime = mimeTypeForFile(QFileInfo(file.path)).name();
|
||||
@@ -82,17 +81,17 @@ bool PrepareAlbumMediaIsWaiting(
|
||||
if (const auto image = base::get_if<Image>(
|
||||
&file.information->media)) {
|
||||
if (ValidPhotoForAlbum(*image)) {
|
||||
file.preview = image->data.scaledToWidth(
|
||||
file.preview = Images::prepareOpaque(image->data.scaledToWidth(
|
||||
std::min(previewWidth, convertScale(image->data.width()))
|
||||
* cIntRetinaFactor(),
|
||||
Qt::SmoothTransformation);
|
||||
Qt::SmoothTransformation));
|
||||
file.preview.setDevicePixelRatio(cRetinaFactor());
|
||||
file.type = PreparedFile::AlbumType::Photo;
|
||||
}
|
||||
} else if (const auto video = base::get_if<Video>(
|
||||
&file.information->media)) {
|
||||
if (ValidVideoForAlbum(*video)) {
|
||||
auto blurred = Images::prepareBlur(video->thumbnail);
|
||||
auto blurred = Images::prepareBlur(Images::prepareOpaque(video->thumbnail));
|
||||
file.preview = std::move(blurred).scaledToWidth(
|
||||
previewWidth * cIntRetinaFactor(),
|
||||
Qt::SmoothTransformation);
|
||||
@@ -287,5 +286,34 @@ PreparedList PreparedList::Reordered(
|
||||
return result;
|
||||
}
|
||||
|
||||
void PreparedList::mergeToEnd(PreparedList &&other) {
|
||||
if (error != Error::None) {
|
||||
return;
|
||||
}
|
||||
if (other.error != Error::None) {
|
||||
error = other.error;
|
||||
errorData = other.errorData;
|
||||
return;
|
||||
}
|
||||
allFilesForCompress = allFilesForCompress && other.allFilesForCompress;
|
||||
files.reserve(files.size() + other.files.size());
|
||||
for (auto &file : other.files) {
|
||||
files.push_back(std::move(file));
|
||||
}
|
||||
if (files.size() > 1 && files.size() <= kMaxAlbumCount) {
|
||||
const auto badIt = ranges::find(
|
||||
files,
|
||||
PreparedFile::AlbumType::None,
|
||||
[](const PreparedFile &file) { return file.type; });
|
||||
albumIsPossible = (badIt == files.end());
|
||||
} else {
|
||||
albumIsPossible = false;
|
||||
}
|
||||
}
|
||||
|
||||
int MaxAlbumItems() {
|
||||
return kMaxAlbumCount;
|
||||
}
|
||||
|
||||
} // namespace Storage
|
||||
|
||||
|
||||
@@ -71,6 +71,7 @@ struct PreparedList {
|
||||
static PreparedList Reordered(
|
||||
PreparedList &&list,
|
||||
std::vector<int> order);
|
||||
void mergeToEnd(PreparedList &&other);
|
||||
|
||||
Error error = Error::None;
|
||||
QString errorData;
|
||||
@@ -87,5 +88,6 @@ PreparedList PrepareMediaFromImage(
|
||||
QImage &&image,
|
||||
QByteArray &&content,
|
||||
int previewWidth);
|
||||
int MaxAlbumItems();
|
||||
|
||||
} // namespace Storage
|
||||
|
||||
@@ -21,7 +21,6 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "storage/storage_shared_media.h"
|
||||
|
||||
#include <rpl/map.h>
|
||||
#include "base/task_queue.h"
|
||||
|
||||
namespace Storage {
|
||||
|
||||
|
||||
@@ -28,18 +28,19 @@ namespace Storage {
|
||||
|
||||
// Allow forward declarations.
|
||||
enum class SharedMediaType : char {
|
||||
Photo = 0,
|
||||
Video = 1,
|
||||
MusicFile = 2,
|
||||
File = 3,
|
||||
VoiceFile = 4,
|
||||
Link = 5,
|
||||
ChatPhoto = 6,
|
||||
RoundVoiceFile = 7,
|
||||
GIF = 8,
|
||||
RoundFile = 9,
|
||||
Photo,
|
||||
Video,
|
||||
PhotoVideo,
|
||||
MusicFile,
|
||||
File,
|
||||
VoiceFile,
|
||||
Link,
|
||||
ChatPhoto,
|
||||
RoundVoiceFile,
|
||||
GIF,
|
||||
RoundFile,
|
||||
|
||||
kCount = 10,
|
||||
kCount,
|
||||
};
|
||||
constexpr auto kSharedMediaTypeCount = static_cast<int>(SharedMediaType::kCount);
|
||||
constexpr bool IsValidSharedMediaType(SharedMediaType type) {
|
||||
|
||||
@@ -20,8 +20,6 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#include "storage/storage_user_photos.h"
|
||||
|
||||
#include "base/task_queue.h"
|
||||
|
||||
namespace Storage {
|
||||
|
||||
void UserPhotos::List::addNew(PhotoId photoId) {
|
||||
|
||||
@@ -59,6 +59,10 @@ inline Widget *CreateChild(
|
||||
return new Widget(parent, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
inline void DestroyChild(QWidget *child) {
|
||||
delete child;
|
||||
}
|
||||
|
||||
template <typename Value>
|
||||
inline void AttachAsChild(not_null<QObject*> parent, Value &&value) {
|
||||
using PlainValue = std::decay_t<Value>;
|
||||
|
||||
257
Telegram/SourceFiles/ui/text_options.cpp
Normal file
257
Telegram/SourceFiles/ui/text_options.cpp
Normal file
@@ -0,0 +1,257 @@
|
||||
/*
|
||||
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-2017 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#include "ui/text_options.h"
|
||||
|
||||
#include "styles/style_window.h"
|
||||
|
||||
namespace Ui {
|
||||
namespace {
|
||||
|
||||
TextParseOptions HistoryTextOptions = {
|
||||
TextParseLinks
|
||||
| TextParseMentions
|
||||
| TextParseHashtags
|
||||
| TextParseMultiline
|
||||
| TextParseRichText
|
||||
| TextParseMarkdown, // flags
|
||||
0, // maxw
|
||||
0, // maxh
|
||||
Qt::LayoutDirectionAuto, // dir
|
||||
};
|
||||
|
||||
TextParseOptions HistoryBotOptions = {
|
||||
TextParseLinks
|
||||
| TextParseMentions
|
||||
| TextParseHashtags
|
||||
| TextParseBotCommands
|
||||
| TextParseMultiline
|
||||
| TextParseRichText
|
||||
| TextParseMarkdown, // flags
|
||||
0, // maxw
|
||||
0, // maxh
|
||||
Qt::LayoutDirectionAuto, // dir
|
||||
};
|
||||
|
||||
TextParseOptions HistoryServiceOptions = {
|
||||
TextParseLinks
|
||||
| TextParseMentions
|
||||
| TextParseHashtags
|
||||
//| TextParseMultiline
|
||||
| TextParseRichText, // flags
|
||||
0, // maxw
|
||||
0, // maxh
|
||||
Qt::LayoutDirectionAuto, // lang-dependent
|
||||
};
|
||||
|
||||
TextParseOptions HistoryTextNoMonoOptions = {
|
||||
TextParseLinks
|
||||
| TextParseMentions
|
||||
| TextParseHashtags
|
||||
| TextParseMultiline
|
||||
| TextParseRichText, // flags
|
||||
0, // maxw
|
||||
0, // maxh
|
||||
Qt::LayoutDirectionAuto, // dir
|
||||
};
|
||||
|
||||
TextParseOptions HistoryBotNoMonoOptions = {
|
||||
TextParseLinks
|
||||
| TextParseMentions
|
||||
| TextParseHashtags
|
||||
| TextParseBotCommands
|
||||
| TextParseMultiline
|
||||
| TextParseRichText, // flags
|
||||
0, // maxw
|
||||
0, // maxh
|
||||
Qt::LayoutDirectionAuto, // dir
|
||||
};
|
||||
|
||||
TextParseOptions TextNameOptions = {
|
||||
0, // flags
|
||||
4096, // maxw
|
||||
1, // maxh
|
||||
Qt::LayoutDirectionAuto, // lang-dependent
|
||||
};
|
||||
|
||||
TextParseOptions TextDialogOptions = {
|
||||
TextParseRichText, // flags
|
||||
0, // maxw is style-dependent
|
||||
1, // maxh
|
||||
Qt::LayoutDirectionAuto, // lang-dependent
|
||||
};
|
||||
|
||||
TextParseOptions WebpageTitleOptions = {
|
||||
TextParseMultiline | TextParseRichText, // flags
|
||||
0, // maxw
|
||||
0, // maxh
|
||||
Qt::LayoutDirectionAuto, // dir
|
||||
};
|
||||
|
||||
TextParseOptions WebpageDescriptionOptions = {
|
||||
TextParseLinks
|
||||
| TextParseMentions
|
||||
| TextParseHashtags
|
||||
| TextParseMultiline
|
||||
| TextParseRichText
|
||||
| TextParseMarkdown, // flags
|
||||
0, // maxw
|
||||
0, // maxh
|
||||
Qt::LayoutDirectionAuto, // dir
|
||||
};
|
||||
|
||||
TextParseOptions TwitterDescriptionOptions = {
|
||||
TextParseLinks
|
||||
| TextParseMentions
|
||||
| TextTwitterMentions
|
||||
| TextParseHashtags
|
||||
| TextTwitterHashtags
|
||||
| TextParseMultiline
|
||||
| TextParseRichText, // flags
|
||||
0, // maxw
|
||||
0, // maxh
|
||||
Qt::LayoutDirectionAuto, // dir
|
||||
};
|
||||
|
||||
TextParseOptions InstagramDescriptionOptions = {
|
||||
TextParseLinks
|
||||
| TextParseMentions
|
||||
| TextInstagramMentions
|
||||
| TextParseHashtags
|
||||
| TextInstagramHashtags
|
||||
| TextParseMultiline
|
||||
| TextParseRichText, // flags
|
||||
0, // maxw
|
||||
0, // maxh
|
||||
Qt::LayoutDirectionAuto, // dir
|
||||
};
|
||||
|
||||
bool UseBotTextOptions(
|
||||
not_null<History*> history,
|
||||
not_null<PeerData*> author) {
|
||||
if (const auto user = history->peer->asUser()) {
|
||||
if (user->botInfo) {
|
||||
return true;
|
||||
}
|
||||
} else if (const auto chat = history->peer->asChat()) {
|
||||
if (chat->botStatus >= 0) {
|
||||
return true;
|
||||
}
|
||||
} else if (const auto group = history->peer->asMegagroup()) {
|
||||
if (group->mgInfo->botStatus >= 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (const auto user = author->asUser()) {
|
||||
if (user->botInfo) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void InitTextOptions() {
|
||||
HistoryServiceOptions.dir
|
||||
= TextNameOptions.dir
|
||||
= TextDialogOptions.dir
|
||||
= cLangDir();
|
||||
TextDialogOptions.maxw = st::columnMaximalWidthLeft * 2;
|
||||
WebpageTitleOptions.maxh = st::webPageTitleFont->height * 2;
|
||||
WebpageTitleOptions.maxw
|
||||
= WebpageDescriptionOptions.maxw
|
||||
= TwitterDescriptionOptions.maxw
|
||||
= InstagramDescriptionOptions.maxw
|
||||
= st::msgMaxWidth
|
||||
- st::msgPadding.left()
|
||||
- st::webPageLeft
|
||||
- st::msgPadding.right();
|
||||
WebpageDescriptionOptions.maxh = st::webPageDescriptionFont->height * 3;
|
||||
}
|
||||
|
||||
const TextParseOptions &ItemTextDefaultOptions() {
|
||||
return HistoryTextOptions;
|
||||
}
|
||||
|
||||
const TextParseOptions &ItemTextBotDefaultOptions() {
|
||||
return HistoryBotOptions;
|
||||
}
|
||||
|
||||
const TextParseOptions &ItemTextNoMonoOptions() {
|
||||
return HistoryTextNoMonoOptions;
|
||||
}
|
||||
|
||||
const TextParseOptions &ItemTextBotNoMonoOptions() {
|
||||
return HistoryBotNoMonoOptions;
|
||||
}
|
||||
|
||||
const TextParseOptions &ItemTextServiceOptions() {
|
||||
return HistoryServiceOptions;
|
||||
}
|
||||
|
||||
const TextParseOptions &WebpageTextTitleOptions() {
|
||||
return WebpageTitleOptions;
|
||||
}
|
||||
|
||||
const TextParseOptions &WebpageTextDescriptionOptions(
|
||||
const QString &siteName) {
|
||||
if (siteName == qstr("Twitter")) {
|
||||
return TwitterDescriptionOptions;
|
||||
} else if (siteName == qstr("Instagram")) {
|
||||
return InstagramDescriptionOptions;
|
||||
}
|
||||
return WebpageDescriptionOptions;
|
||||
}
|
||||
|
||||
const TextParseOptions &NameTextOptions() {
|
||||
return TextNameOptions;
|
||||
}
|
||||
|
||||
const TextParseOptions &DialogTextOptions() {
|
||||
return TextDialogOptions;
|
||||
}
|
||||
|
||||
const TextParseOptions &ItemTextOptions(
|
||||
not_null<History*> history,
|
||||
not_null<PeerData*> author) {
|
||||
return UseBotTextOptions(history, author)
|
||||
? HistoryBotOptions
|
||||
: HistoryTextOptions;
|
||||
}
|
||||
|
||||
const TextParseOptions &ItemTextOptions(not_null<const HistoryItem*> item) {
|
||||
return ItemTextOptions(item->history(), item->author());
|
||||
}
|
||||
|
||||
const TextParseOptions &ItemTextNoMonoOptions(
|
||||
not_null<History*> history,
|
||||
not_null<PeerData*> author) {
|
||||
return UseBotTextOptions(history, author)
|
||||
? HistoryBotNoMonoOptions
|
||||
: HistoryTextNoMonoOptions;
|
||||
}
|
||||
|
||||
const TextParseOptions &ItemTextNoMonoOptions(
|
||||
not_null<const HistoryItem*> item) {
|
||||
return ItemTextNoMonoOptions(item->history(), item->author());
|
||||
}
|
||||
|
||||
} // namespace Ui
|
||||
49
Telegram/SourceFiles/ui/text_options.h
Normal file
49
Telegram/SourceFiles/ui/text_options.h
Normal file
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
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-2017 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
namespace Ui {
|
||||
|
||||
void InitTextOptions();
|
||||
|
||||
const TextParseOptions &ItemTextDefaultOptions();
|
||||
const TextParseOptions &ItemTextBotDefaultOptions();
|
||||
const TextParseOptions &ItemTextNoMonoOptions();
|
||||
const TextParseOptions &ItemTextBotNoMonoOptions();
|
||||
const TextParseOptions &ItemTextServiceOptions();
|
||||
|
||||
const TextParseOptions &WebpageTextTitleOptions();
|
||||
const TextParseOptions &WebpageTextDescriptionOptions(
|
||||
const QString &siteName = QString());
|
||||
|
||||
const TextParseOptions &NameTextOptions();
|
||||
const TextParseOptions &DialogTextOptions();
|
||||
|
||||
const TextParseOptions &ItemTextOptions(
|
||||
not_null<History*> history,
|
||||
not_null<PeerData*> author);
|
||||
const TextParseOptions &ItemTextNoMonoOptions(
|
||||
not_null<History*> history,
|
||||
not_null<PeerData*> author);
|
||||
const TextParseOptions &ItemTextOptions(not_null<const HistoryItem*> item);
|
||||
const TextParseOptions &ItemTextNoMonoOptions(not_null<const HistoryItem*> item);
|
||||
|
||||
} // namespace Ui
|
||||
@@ -2467,6 +2467,24 @@ void InputArea::Inner::contextMenuEvent(QContextMenuEvent *e) {
|
||||
}
|
||||
}
|
||||
|
||||
bool InputArea::Inner::canInsertFromMimeData(const QMimeData *source) const {
|
||||
if (source
|
||||
&& f()->_mimeDataHook
|
||||
&& f()->_mimeDataHook(source, MimeAction::Check)) {
|
||||
return true;
|
||||
}
|
||||
return QTextEdit::canInsertFromMimeData(source);
|
||||
}
|
||||
|
||||
void InputArea::Inner::insertFromMimeData(const QMimeData *source) {
|
||||
if (source
|
||||
&& f()->_mimeDataHook
|
||||
&& f()->_mimeDataHook(source, MimeAction::Insert)) {
|
||||
return;
|
||||
}
|
||||
return QTextEdit::insertFromMimeData(source);
|
||||
}
|
||||
|
||||
void InputArea::resizeEvent(QResizeEvent *e) {
|
||||
refreshPlaceholder();
|
||||
_inner->setGeometry(rect().marginsRemoved(_st.textMargins));
|
||||
|
||||
@@ -387,6 +387,17 @@ public:
|
||||
_inner->clearFocus();
|
||||
}
|
||||
|
||||
enum class MimeAction {
|
||||
Check,
|
||||
Insert,
|
||||
};
|
||||
using MimeDataHook = base::lambda<bool(
|
||||
not_null<const QMimeData*> data,
|
||||
MimeAction action)>;
|
||||
void setMimeDataHook(MimeDataHook hook) {
|
||||
_mimeDataHook = std::move(hook);
|
||||
}
|
||||
|
||||
private slots:
|
||||
void onTouchTimer();
|
||||
|
||||
@@ -441,6 +452,8 @@ private:
|
||||
void keyPressEvent(QKeyEvent *e) override;
|
||||
void contextMenuEvent(QContextMenuEvent *e) override;
|
||||
|
||||
bool canInsertFromMimeData(const QMimeData *source) const override;
|
||||
void insertFromMimeData(const QMimeData *source) override;
|
||||
QMimeData *createMimeDataFromSelection() const override;
|
||||
|
||||
private:
|
||||
@@ -504,6 +517,7 @@ private:
|
||||
QPoint _touchStart;
|
||||
|
||||
bool _correcting = false;
|
||||
MimeDataHook _mimeDataHook;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "ui/widgets/input_fields.h"
|
||||
#include "ui/widgets/scroll_area.h"
|
||||
#include "ui/effects/cross_animation.h"
|
||||
#include "ui/text_options.h"
|
||||
#include "lang/lang_keys.h"
|
||||
|
||||
namespace Ui {
|
||||
@@ -43,7 +44,7 @@ MultiSelect::Item::Item(const style::MultiSelectItem &st, uint64 id, const QStri
|
||||
}
|
||||
|
||||
void MultiSelect::Item::setText(const QString &text) {
|
||||
_text.setText(_st.style, text, _textNameOptions);
|
||||
_text.setText(_st.style, text, NameTextOptions());
|
||||
_width = _st.height + _st.padding.left() + _text.maxWidth() + _st.padding.right();
|
||||
accumulate_min(_width, _st.maxWidth);
|
||||
}
|
||||
|
||||
@@ -120,8 +120,9 @@ void SlideWrap<RpWidget>::animationStep() {
|
||||
}
|
||||
auto shouldBeHidden = !_toggled && !_animation.animating();
|
||||
if (shouldBeHidden != isHidden()) {
|
||||
const auto guard = make_weak(this);
|
||||
setVisible(!shouldBeHidden);
|
||||
if (shouldBeHidden) {
|
||||
if (shouldBeHidden && guard) {
|
||||
SendPendingMoveResizeEvents(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,6 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "ui/widgets/checkbox.h"
|
||||
#include "ui/widgets/multi_select.h"
|
||||
#include "base/parse_helper.h"
|
||||
#include "base/task_queue.h"
|
||||
#include "base/zlib_help.h"
|
||||
#include "ui/toast/toast.h"
|
||||
#include "core/file_utilities.h"
|
||||
@@ -718,7 +717,9 @@ Editor::Editor(QWidget*, const QString &path)
|
||||
|
||||
// This could be from inner->_context observable notification.
|
||||
// We should not destroy it while iterating in subscribers.
|
||||
base::TaskQueue::Main().Put(base::lambda_guarded(this, [this] { closeEditor(); }));
|
||||
crl::on_main(this, [=] {
|
||||
closeEditor();
|
||||
});
|
||||
});
|
||||
_inner->setFocusCallback([this] {
|
||||
App::CallDelayed(2 * st::boxDuration, this, [this] { _select->setInnerFocus(); });
|
||||
|
||||
@@ -23,6 +23,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
#include "window/themes/window_theme.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "platform/platform_window_title.h"
|
||||
#include "ui/text_options.h"
|
||||
#include "styles/style_widgets.h"
|
||||
#include "styles/style_window.h"
|
||||
#include "styles/style_mediaview.h"
|
||||
@@ -213,13 +214,13 @@ void Generator::prepare() {
|
||||
|
||||
void Generator::addRow(QString name, int peerIndex, QString date, QString text) {
|
||||
Row row;
|
||||
row.name.setText(st::msgNameStyle, name, _textNameOptions);
|
||||
row.name.setText(st::msgNameStyle, name, Ui::NameTextOptions());
|
||||
|
||||
row.letters = fillLetters(name);
|
||||
|
||||
row.peerIndex = peerIndex;
|
||||
row.date = date;
|
||||
row.text.setRichText(st::dialogsTextStyle, text, _textDlgOptions);
|
||||
row.text.setRichText(st::dialogsTextStyle, text, Ui::DialogTextOptions());
|
||||
_rows.push_back(std::move(row));
|
||||
}
|
||||
|
||||
@@ -268,7 +269,7 @@ int Generator::computeInfoWidth(Status status, QString date) {
|
||||
void Generator::addTextBubble(QString text, QString date, Status status) {
|
||||
Bubble bubble;
|
||||
auto skipBlock = computeSkipBlock(status, date);
|
||||
bubble.text.setRichText(st::messageTextStyle, text + textcmdSkipBlock(skipBlock.width(), skipBlock.height()), _historyTextOptions);
|
||||
bubble.text.setRichText(st::messageTextStyle, text + textcmdSkipBlock(skipBlock.width(), skipBlock.height()), Ui::ItemTextDefaultOptions());
|
||||
|
||||
auto width = _history.width() - st::msgMargin.left() - st::msgMargin.right();
|
||||
accumulate_min(width, st::msgPadding.left() + bubble.text.maxWidth() + st::msgPadding.right());
|
||||
@@ -292,7 +293,7 @@ void Generator::addPhotoBubble(QString image, QString caption, QString date, Sta
|
||||
bubble.photoWidth = convertScale(bubble.photo.width() / 2);
|
||||
bubble.photoHeight = convertScale(bubble.photo.height() / 2);
|
||||
auto skipBlock = computeSkipBlock(status, date);
|
||||
bubble.text.setRichText(st::messageTextStyle, caption + textcmdSkipBlock(skipBlock.width(), skipBlock.height()), _historyTextOptions);
|
||||
bubble.text.setRichText(st::messageTextStyle, caption + textcmdSkipBlock(skipBlock.width(), skipBlock.height()), Ui::ItemTextDefaultOptions());
|
||||
|
||||
auto width = _history.width() - st::msgMargin.left() - st::msgMargin.right();
|
||||
accumulate_min(width, bubble.photoWidth);
|
||||
@@ -325,7 +326,7 @@ void Generator::generateData() {
|
||||
_rows.back().status = Status::Received;
|
||||
addRow("Davy Jones", 5, "4:00", textcmdLink(1, "Keynote.pdf"));
|
||||
|
||||
_topBarName.setText(st::msgNameStyle, "Eva Summer", _textNameOptions);
|
||||
_topBarName.setText(st::msgNameStyle, "Eva Summer", Ui::NameTextOptions());
|
||||
_topBarStatus = "online";
|
||||
_topBarStatusActive = true;
|
||||
|
||||
@@ -345,8 +346,8 @@ void Generator::generateData() {
|
||||
_bubbles.back().attached = true;
|
||||
_bubbles.back().tail = true;
|
||||
addTextBubble("Reminds me of a Chinese proverb: the best time to plant a tree was 20 years ago. The second best time is now.", "11:00", Status::None);
|
||||
_bubbles.back().replyName.setText(st::msgNameStyle, "Alex Cassio", _textNameOptions);
|
||||
_bubbles.back().replyText.setText(st::messageTextStyle, "Mark Twain said that " + QString() + QChar(9757) + QChar(55356) + QChar(57339), _textDlgOptions);
|
||||
_bubbles.back().replyName.setText(st::msgNameStyle, "Alex Cassio", Ui::NameTextOptions());
|
||||
_bubbles.back().replyText.setText(st::messageTextStyle, "Mark Twain said that " + QString() + QChar(9757) + QChar(55356) + QChar(57339), Ui::DialogTextOptions());
|
||||
}
|
||||
|
||||
Generator::Generator(const Instance &theme, const CurrentData ¤t)
|
||||
|
||||
2
Telegram/ThirdParty/crl
vendored
2
Telegram/ThirdParty/crl
vendored
Submodule Telegram/ThirdParty/crl updated: 6127f98309...705a5fd616
2
Telegram/ThirdParty/libtgvoip
vendored
2
Telegram/ThirdParty/libtgvoip
vendored
Submodule Telegram/ThirdParty/libtgvoip updated: 5519789e1a...0b53884c8b
@@ -1,6 +1,6 @@
|
||||
AppVersion 1002004
|
||||
AppVersion 1002007
|
||||
AppVersionStrMajor 1.2
|
||||
AppVersionStrSmall 1.2.4
|
||||
AppVersionStr 1.2.4
|
||||
AppVersionStrSmall 1.2.7
|
||||
AppVersionStr 1.2.7
|
||||
AlphaChannel 1
|
||||
BetaVersion 0
|
||||
|
||||
@@ -52,6 +52,7 @@
|
||||
'<(crl_src_loc)/common/crl_common_list.h',
|
||||
'<(crl_src_loc)/common/crl_common_on_main.cpp',
|
||||
'<(crl_src_loc)/common/crl_common_on_main.h',
|
||||
'<(crl_src_loc)/common/crl_common_on_main_guarded.h',
|
||||
'<(crl_src_loc)/common/crl_common_queue.cpp',
|
||||
'<(crl_src_loc)/common/crl_common_queue.h',
|
||||
'<(crl_src_loc)/common/crl_common_sync.h',
|
||||
|
||||
@@ -109,6 +109,9 @@
|
||||
'OptimizeReferences': '2',
|
||||
'LinkTimeCodeGeneration': '1', # /LTCG
|
||||
},
|
||||
'VCLibrarianTool': {
|
||||
'LinkTimeCodeGeneration': 'true', # /LTCG
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -22,8 +22,6 @@
|
||||
<(src_loc)/base/qthelp_url.h
|
||||
<(src_loc)/base/runtime_composer.cpp
|
||||
<(src_loc)/base/runtime_composer.h
|
||||
<(src_loc)/base/task_queue.cpp
|
||||
<(src_loc)/base/task_queue.h
|
||||
<(src_loc)/base/timer.cpp
|
||||
<(src_loc)/base/timer.h
|
||||
<(src_loc)/base/type_traits.h
|
||||
@@ -139,6 +137,8 @@
|
||||
<(src_loc)/chat_helpers/tabbed_selector.cpp
|
||||
<(src_loc)/chat_helpers/tabbed_selector.h
|
||||
<(src_loc)/core/basic_types.h
|
||||
<(src_loc)/core/changelogs.cpp
|
||||
<(src_loc)/core/changelogs.h
|
||||
<(src_loc)/core/click_handler.cpp
|
||||
<(src_loc)/core/click_handler.h
|
||||
<(src_loc)/core/click_handler_types.cpp
|
||||
@@ -187,6 +187,7 @@
|
||||
<(src_loc)/data/data_types.h
|
||||
<(src_loc)/data/data_user_photos.cpp
|
||||
<(src_loc)/data/data_user_photos.h
|
||||
<(src_loc)/data/data_web_page.cpp
|
||||
<(src_loc)/data/data_web_page.h
|
||||
<(src_loc)/dialogs/dialogs_common.h
|
||||
<(src_loc)/dialogs/dialogs_indexed_list.cpp
|
||||
@@ -343,6 +344,8 @@
|
||||
<(src_loc)/media/view/media_clip_playback.h
|
||||
<(src_loc)/media/view/media_clip_volume_controller.cpp
|
||||
<(src_loc)/media/view/media_clip_volume_controller.h
|
||||
<(src_loc)/media/view/media_view_group_thumbs.cpp
|
||||
<(src_loc)/media/view/media_view_group_thumbs.h
|
||||
<(src_loc)/media/media_audio.cpp
|
||||
<(src_loc)/media/media_audio.h
|
||||
<(src_loc)/media/media_audio_capture.cpp
|
||||
@@ -628,6 +631,8 @@
|
||||
<(src_loc)/ui/search_field_controller.h
|
||||
<(src_loc)/ui/special_buttons.cpp
|
||||
<(src_loc)/ui/special_buttons.h
|
||||
<(src_loc)/ui/text_options.cpp
|
||||
<(src_loc)/ui/text_options.h
|
||||
<(src_loc)/ui/twidget.cpp
|
||||
<(src_loc)/ui/twidget.h
|
||||
<(src_loc)/ui/unread_badge.cpp
|
||||
|
||||
@@ -1,3 +1,18 @@
|
||||
1.2.7 alpha (31.12.17)
|
||||
|
||||
- Use fast reply button in group chats.
|
||||
- Select a message you want to reply to by pressing Ctrl+Up and Ctrl+Down.
|
||||
|
||||
1.2.6 (30.12.17)
|
||||
|
||||
- Grouped Photos. Group media into an album when sharing multiple photos and videos. Choose the exact order of media you send.
|
||||
|
||||
1.2.5 alpha (29.12.17)
|
||||
|
||||
- When viewing a photo from an album, you'll see other pictures from the same group as thumbnails in the lower part of the screen.
|
||||
- When composing an album paste additional media from the clipboard.
|
||||
- Bug fixes and other minor improvements.
|
||||
|
||||
1.2.4 alpha (26.12.17)
|
||||
|
||||
- Group media into an album when sharing multiple photos and videos.
|
||||
|
||||
Reference in New Issue
Block a user