First working code for sending albums.

This commit is contained in:
John Preston 2017-12-19 20:57:42 +04:00
parent 711aa51046
commit 3b3a705a67
30 changed files with 1789 additions and 731 deletions

View file

@ -33,6 +33,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "mainwidget.h"
#include "boxes/add_contact_box.h"
#include "history/history_message.h"
#include "history/history_media_types.h"
#include "history/history_item_components.h"
#include "storage/localstorage.h"
#include "auth_session.h"
@ -41,9 +42,11 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "window/notifications_manager.h"
#include "chat_helpers/message_field.h"
#include "chat_helpers/stickers.h"
#include "storage/localimageloader.h"
#include "storage/storage_facade.h"
#include "storage/storage_shared_media.h"
#include "storage/storage_user_photos.h"
#include "storage/storage_media_prepare.h"
#include "data/data_sparse_ids.h"
#include "data/data_search_controller.h"
#include "data/data_channel_admins.h"
@ -59,6 +62,76 @@ constexpr auto kUnreadMentionsFirstRequestLimit = 10;
constexpr auto kUnreadMentionsNextRequestLimit = 100;
constexpr auto kSharedMediaLimit = 100;
constexpr auto kReadFeaturedSetsTimeout = TimeMs(1000);
constexpr auto kFileLoaderQueueStopTimeout = TimeMs(5000);
bool IsSilentPost(not_null<HistoryItem*> item, bool silent) {
const auto history = item->history();
return silent
&& history->peer->isChannel()
&& !history->peer->isMegagroup();
}
MTPVector<MTPDocumentAttribute> ComposeSendingDocumentAttributes(
not_null<DocumentData*> document) {
const auto filenameAttribute = MTP_documentAttributeFilename(
MTP_string(document->filename()));
const auto dimensions = document->dimensions;
auto attributes = QVector<MTPDocumentAttribute>(1, filenameAttribute);
if (dimensions.width() > 0 && dimensions.height() > 0) {
const auto duration = document->duration();
if (duration >= 0) {
auto flags = MTPDdocumentAttributeVideo::Flags(0);
if (document->isVideoMessage()) {
flags |= MTPDdocumentAttributeVideo::Flag::f_round_message;
}
attributes.push_back(MTP_documentAttributeVideo(
MTP_flags(flags),
MTP_int(duration),
MTP_int(dimensions.width()),
MTP_int(dimensions.height())));
} else {
attributes.push_back(MTP_documentAttributeImageSize(
MTP_int(dimensions.width()),
MTP_int(dimensions.height())));
}
}
if (document->type == AnimatedDocument) {
attributes.push_back(MTP_documentAttributeAnimated());
} else if (document->type == StickerDocument && document->sticker()) {
attributes.push_back(MTP_documentAttributeSticker(
MTP_flags(0),
MTP_string(document->sticker()->alt),
document->sticker()->set,
MTPMaskCoords()));
} else if (const auto song = document->song()) {
const auto flags = MTPDdocumentAttributeAudio::Flag::f_title
| MTPDdocumentAttributeAudio::Flag::f_performer;
attributes.push_back(MTP_documentAttributeAudio(
MTP_flags(flags),
MTP_int(song->duration),
MTP_string(song->title),
MTP_string(song->performer),
MTPstring()));
} else if (const auto voice = document->voice()) {
const auto flags = MTPDdocumentAttributeAudio::Flag::f_voice
| MTPDdocumentAttributeAudio::Flag::f_waveform;
attributes.push_back(MTP_documentAttributeAudio(
MTP_flags(flags),
MTP_int(voice->duration),
MTPstring(),
MTPstring(),
MTP_bytes(documentWaveformEncode5bit(voice->waveform))));
}
return MTP_vector<MTPDocumentAttribute>(attributes);
}
FileLoadTo FileLoadTaskOptions(const ApiWrap::SendOptions &options) {
const auto peer = options.history->peer;
return FileLoadTo(
peer->id,
peer->notifySilentPosts(),
options.replyTo);
}
} // namespace
@ -67,7 +140,8 @@ ApiWrap::ApiWrap(not_null<AuthSession*> session)
, _messageDataResolveDelayed([this] { resolveMessageDatas(); })
, _webPagesTimer([this] { resolveWebPages(); })
, _draftsSaveTimer([this] { saveDraftsToCloud(); })
, _featuredSetsReadTimer([this] { readFeaturedSets(); }) {
, _featuredSetsReadTimer([this] { readFeaturedSets(); })
, _fileLoader(std::make_unique<TaskQueue>(kFileLoaderQueueStopTimeout)) {
}
void ApiWrap::start() {
@ -129,10 +203,26 @@ void ApiWrap::addLocalChangelogs(int oldAppVersion) {
}
}
void ApiWrap::applyUpdates(const MTPUpdates &updates, uint64 sentMessageRandomId) {
void ApiWrap::applyUpdates(
const MTPUpdates &updates,
uint64 sentMessageRandomId) {
App::main()->feedUpdates(updates, sentMessageRandomId);
}
void ApiWrap::sendMessageFail(const RPCError &error) {
if (error.type() == qstr("PEER_FLOOD")) {
Ui::show(Box<InformBox>(
PeerFloodErrorText(PeerFloodType::Send)));
} else if (error.type() == qstr("USER_BANNED_IN_CHANNEL")) {
const auto link = textcmdLink(
Messenger::Instance().createInternalLinkFull(qsl("spambot")),
lang(lng_cant_more_info));
Ui::show(Box<InformBox>(lng_error_public_groups_denied(
lt_more_info,
link)));
}
}
void ApiWrap::requestMessageData(ChannelData *channel, MsgId msgId, RequestMessageDataCallback callback) {
auto &req = (channel ? _channelMessageDataRequests[channel][msgId] : _messageDataRequests[msgId]);
if (callback) {
@ -2567,17 +2657,12 @@ void ApiWrap::sendSharedContact(
const auto history = options.history;
const auto peer = history->peer;
const auto randomId = rand_value<uint64>();
const auto newId = FullMsgId(history->channelId(), clientMsgId());
const auto channelPost = peer->isChannel() && !peer->isMegagroup();
const auto silentPost = channelPost && peer->notifySilentPosts();
auto flags = NewMessageFlags(peer) | MTPDmessage::Flag::f_media;
auto sendFlags = MTPmessages_SendMedia::Flags(0);
if (options.replyTo) {
flags |= MTPDmessage::Flag::f_reply_to_msg_id;
sendFlags |= MTPmessages_SendMedia::Flag::f_reply_to_msg_id;
}
if (channelPost) {
flags |= MTPDmessage::Flag::f_views;
@ -2588,14 +2673,11 @@ void ApiWrap::sendSharedContact(
} else {
flags |= MTPDmessage::Flag::f_from_id;
}
if (silentPost) {
sendFlags |= MTPmessages_SendMedia::Flag::f_silent;
}
const auto messageFromId = channelPost ? 0 : Auth().userId();
const auto messagePostAuthor = channelPost
? (Auth().user()->firstName + ' ' + Auth().user()->lastName)
: QString();
history->addNewMessage(
const auto item = history->addNewMessage(
MTP_message(
MTP_flags(flags),
MTP_int(newId.msg),
@ -2619,35 +2701,310 @@ void ApiWrap::sendSharedContact(
MTPlong()),
NewMessageUnread);
const auto media = MTP_inputMediaContact(
MTP_string(phone),
MTP_string(firstName),
MTP_string(lastName));
sendMedia(item, media, peer->notifySilentPosts());
}
void ApiWrap::sendVoiceMessage(
QByteArray result,
VoiceWaveform waveform,
int duration,
const SendOptions &options) {
const auto caption = QString();
const auto to = FileLoadTaskOptions(options);
_fileLoader->addTask(std::make_unique<FileLoadTask>(
result,
duration,
waveform,
to,
caption));
}
void ApiWrap::sendFiles(
Storage::PreparedList &&list,
const QByteArray &content,
const QImage &image,
SendMediaType type,
QString caption,
std::shared_ptr<SendingAlbum> album,
const SendOptions &options) {
if (list.files.size() > 1 && !caption.isEmpty()) {
auto message = MainWidget::MessageToSend(options.history);
message.textWithTags = { caption, TextWithTags::Tags() };
message.replyTo = options.replyTo;
message.clearDraft = false;
App::main()->sendMessage(message);
caption = QString();
}
const auto to = FileLoadTaskOptions(options);
auto tasks = std::vector<std::unique_ptr<Task>>();
tasks.reserve(list.files.size());
for (auto &file : list.files) {
if (album) {
switch (file.type) {
case Storage::PreparedFile::AlbumType::Photo:
type = SendMediaType::Photo;
break;
case Storage::PreparedFile::AlbumType::Video:
type = SendMediaType::File;
break;
default: Unexpected("AlbumType in uploadFilesAfterConfirmation");
}
}
if (file.path.isEmpty() && (!image.isNull() || !content.isNull())) {
tasks.push_back(std::make_unique<FileLoadTask>(
content,
image,
type,
to,
caption,
album));
} else {
tasks.push_back(std::make_unique<FileLoadTask>(
file.path,
std::move(file.information),
type,
to,
caption,
album));
}
}
if (album) {
_sendingAlbums.emplace(album->groupId, album);
album->items.reserve(tasks.size());
for (const auto &task : tasks) {
album->items.push_back(SendingAlbum::Item(task->id()));
}
}
_fileLoader->addTasks(std::move(tasks));
}
void ApiWrap::sendFile(
const QByteArray &fileContent,
SendMediaType type,
const SendOptions &options) {
auto to = FileLoadTaskOptions(options);
auto caption = QString();
_fileLoader->addTask(std::make_unique<FileLoadTask>(
fileContent,
QImage(),
type,
to,
caption));
}
void ApiWrap::sendUploadedPhoto(
FullMsgId localId,
const MTPInputFile &file,
bool silent) {
if (const auto item = App::histItemById(localId)) {
const auto caption = item->getMedia()
? item->getMedia()->getCaption()
: TextWithEntities();
const auto media = MTP_inputMediaUploadedPhoto(
MTP_flags(0),
file,
MTP_string(caption.text),
MTPVector<MTPInputDocument>(),
MTP_int(0));
if (const auto groupId = item->groupId()) {
uploadAlbumMedia(item, groupId, media, silent);
} else {
sendMedia(item, media, silent);
}
}
}
void ApiWrap::sendUploadedDocument(
FullMsgId localId,
const MTPInputFile &file,
const base::optional<MTPInputFile> &thumb,
bool silent) {
if (const auto item = App::histItemById(localId)) {
auto media = item->getMedia();
if (auto document = media ? media->getDocument() : nullptr) {
const auto caption = media->getCaption();
const auto groupId = item->groupId();
const auto flags = MTPDinputMediaUploadedDocument::Flags(0)
| (thumb
? MTPDinputMediaUploadedDocument::Flag::f_thumb
: MTPDinputMediaUploadedDocument::Flag(0))
| (groupId
? MTPDinputMediaUploadedDocument::Flag::f_nosound_video
: MTPDinputMediaUploadedDocument::Flag(0));
const auto media = MTP_inputMediaUploadedDocument(
MTP_flags(flags),
file,
thumb ? *thumb : MTPInputFile(),
MTP_string(document->mimeString()),
ComposeSendingDocumentAttributes(document),
MTP_string(caption.text),
MTPVector<MTPInputDocument>(),
MTP_int(0));
if (groupId) {
uploadAlbumMedia(item, groupId, media, silent);
} else {
sendMedia(item, media, silent);
}
}
}
}
void ApiWrap::uploadAlbumMedia(
not_null<HistoryItem*> item,
const MessageGroupId &groupId,
const MTPInputMedia &media,
bool silent) {
const auto localId = item->fullId();
const auto failed = [this] {
};
request(MTPmessages_UploadMedia(
item->history()->peer->input,
media
)).done([=](const MTPMessageMedia &result) {
const auto item = App::histItemById(localId);
if (!item) {
failed();
}
switch (result.type()) {
case mtpc_messageMediaPhoto: {
const auto &data = result.c_messageMediaPhoto();
if (data.vphoto.type() != mtpc_photo) {
failed();
return;
}
const auto &photo = data.vphoto.c_photo();
const auto flags = MTPDinputMediaPhoto::Flags(0)
| (data.has_ttl_seconds()
? MTPDinputMediaPhoto::Flag::f_ttl_seconds
: MTPDinputMediaPhoto::Flag(0));
const auto media = MTP_inputMediaPhoto(
MTP_flags(flags),
MTP_inputPhoto(photo.vid, photo.vaccess_hash),
data.has_caption() ? data.vcaption : MTP_string(QString()),
data.has_ttl_seconds() ? data.vttl_seconds : MTPint());
trySendAlbum(item, groupId, media, silent);
} break;
case mtpc_messageMediaDocument: {
const auto &data = result.c_messageMediaDocument();
if (data.vdocument.type() != mtpc_document) {
failed();
return;
}
const auto &document = data.vdocument.c_document();
const auto flags = MTPDinputMediaDocument::Flags(0)
| (data.has_ttl_seconds()
? MTPDinputMediaDocument::Flag::f_ttl_seconds
: MTPDinputMediaDocument::Flag(0));
const auto media = MTP_inputMediaDocument(
MTP_flags(flags),
MTP_inputDocument(document.vid, document.vaccess_hash),
data.has_caption() ? data.vcaption : MTP_string(QString()),
data.has_ttl_seconds() ? data.vttl_seconds : MTPint());
trySendAlbum(item, groupId, media, silent);
} break;
}
}).fail([=](const RPCError &error) {
failed();
}).send();
}
void ApiWrap::trySendAlbum(
not_null<HistoryItem*> item,
const MessageGroupId &groupId,
const MTPInputMedia &media,
bool silent) {
const auto localId = item->fullId();
const auto randomId = rand_value<uint64>();
App::historyRegRandom(randomId, localId);
const auto medias = completeAlbum(localId, groupId, media, randomId);
if (medias.empty()) {
return;
}
const auto history = item->history();
const auto replyTo = item->replyToId();
const auto flags = MTPmessages_SendMultiMedia::Flags(0)
| (replyTo
? MTPmessages_SendMultiMedia::Flag::f_reply_to_msg_id
: MTPmessages_SendMultiMedia::Flag(0))
| (IsSilentPost(item, silent)
? MTPmessages_SendMultiMedia::Flag::f_silent
: MTPmessages_SendMultiMedia::Flag(0));
history->sendRequestId = request(MTPmessages_SendMultiMedia(
MTP_flags(flags),
history->peer->input,
MTP_int(replyTo),
MTP_vector<MTPInputSingleMedia>(medias)
)).done([=](const MTPUpdates &result) { applyUpdates(result);
}).fail([=](const RPCError &error) { sendMessageFail(error);
}).afterRequest(history->sendRequestId
).send();
}
void ApiWrap::sendMedia(
not_null<HistoryItem*> item,
const MTPInputMedia &media,
bool silent) {
const auto randomId = rand_value<uint64>();
App::historyRegRandom(randomId, item->fullId());
const auto history = item->history();
const auto replyTo = item->replyToId();
const auto flags = MTPmessages_SendMedia::Flags(0)
| (replyTo
? MTPmessages_SendMedia::Flag::f_reply_to_msg_id
: MTPmessages_SendMedia::Flag(0))
| (IsSilentPost(item, silent)
? MTPmessages_SendMedia::Flag::f_silent
: MTPmessages_SendMedia::Flag(0));
history->sendRequestId = request(MTPmessages_SendMedia(
MTP_flags(sendFlags),
peer->input,
MTP_int(options.replyTo),
MTP_inputMediaContact(
MTP_string(phone),
MTP_string(firstName),
MTP_string(lastName)),
MTP_flags(flags),
history->peer->input,
MTP_int(replyTo),
media,
MTP_long(randomId),
MTPnullMarkup
)).done([=](const MTPUpdates &result) {
applyUpdates(result);
}).fail([](const RPCError &error) {
if (error.type() == qstr("PEER_FLOOD")) {
Ui::show(Box<InformBox>(
PeerFloodErrorText(PeerFloodType::Send)));
} else if (error.type() == qstr("USER_BANNED_IN_CHANNEL")) {
const auto link = textcmdLink(
Messenger::Instance().createInternalLinkFull(qsl("spambot")),
lang(lng_cant_more_info));
Ui::show(Box<InformBox>(lng_error_public_groups_denied(
lt_more_info,
link)));
}
}).afterRequest(
history->sendRequestId
)).done([=](const MTPUpdates &result) { applyUpdates(result);
}).fail([=](const RPCError &error) { sendMessageFail(error);
}).afterRequest(history->sendRequestId
).send();
}
App::historyRegRandom(randomId, newId);
QVector<MTPInputSingleMedia> ApiWrap::completeAlbum(
FullMsgId localId,
const MessageGroupId &groupId,
const MTPInputMedia &media,
uint64 randomId) {
const auto albumIt = _sendingAlbums.find(groupId.raw());
Assert(albumIt != _sendingAlbums.end());
const auto &album = albumIt->second;
const auto proj = [](const SendingAlbum::Item &item) {
return item.msgId;
};
const auto itemIt = ranges::find(album->items, localId, proj);
Assert(itemIt != album->items.end());
Assert(!itemIt->media);
itemIt->media = MTP_inputSingleMedia(media, MTP_long(randomId));
auto result = QVector<MTPInputSingleMedia>();
result.reserve(album->items.size());
for (const auto &item : album->items) {
if (!item.media) {
return {};
}
result.push_back(*item.media);
}
return result;
}
void ApiWrap::readServerHistory(not_null<History*> history) {

View file

@ -28,14 +28,18 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "base/flat_set.h"
#include "chat_helpers/stickers.h"
class TaskQueue;
class AuthSession;
enum class SparseIdsLoadDirection;
struct MessageGroupId;
struct SendingAlbum;
enum class SendMediaType;
namespace Storage {
enum class SharedMediaType : char;
struct PreparedList;
} // namespace Storage
enum class SparseIdsLoadDirection;
namespace Api {
inline const MTPVector<MTPChat> *getChatsFromMessagesChats(const MTPmessages_Chats &chats) {
@ -186,6 +190,34 @@ public:
void readServerHistory(not_null<History*> history);
void readServerHistoryForce(not_null<History*> history);
void sendVoiceMessage(
QByteArray result,
VoiceWaveform waveform,
int duration,
const SendOptions &options);
void sendFiles(
Storage::PreparedList &&list,
const QByteArray &content,
const QImage &image,
SendMediaType type,
QString caption,
std::shared_ptr<SendingAlbum> album,
const SendOptions &options);
void sendFile(
const QByteArray &fileContent,
SendMediaType type,
const SendOptions &options);
void sendUploadedPhoto(
FullMsgId localId,
const MTPInputFile &file,
bool silent);
void sendUploadedDocument(
FullMsgId localId,
const MTPInputFile &file,
const base::optional<MTPInputFile> &thumb,
bool silent);
~ApiWrap();
private:
@ -283,6 +315,26 @@ private:
void applyAffectedMessages(
not_null<PeerData*> peer,
const MTPmessages_AffectedMessages &result);
void sendMessageFail(const RPCError &error);
void uploadAlbumMedia(
not_null<HistoryItem*> item,
const MessageGroupId &groupId,
const MTPInputMedia &media,
bool silent);
void trySendAlbum(
not_null<HistoryItem*> item,
const MessageGroupId &groupId,
const MTPInputMedia &media,
bool silent);
QVector<MTPInputSingleMedia> completeAlbum(
FullMsgId localId,
const MessageGroupId &groupId,
const MTPInputMedia &media,
uint64 randomId);
void sendMedia(
not_null<HistoryItem*> item,
const MTPInputMedia &media,
bool silent);
not_null<AuthSession*> _session;
mtpRequestId _changelogSubscription = 0;
@ -375,6 +427,8 @@ private:
};
base::flat_map<not_null<PeerData*>, ReadRequest> _readRequests;
base::flat_map<not_null<PeerData*>, MsgId> _readRequestsPending;
std::unique_ptr<TaskQueue> _fileLoader;
base::flat_map<uint64, std::shared_ptr<SendingAlbum>> _sendingAlbums;
base::Observable<PeerData*> _fullPeerUpdated;

View file

@ -1394,7 +1394,7 @@ namespace {
::photosData.erase(i);
}
convert->id = photo;
convert->uploadingData.reset();
convert->uploadingData = nullptr;
}
if (date) {
convert->access = access;

View file

@ -716,3 +716,5 @@ groupStickersField: InputField(contactsSearchField) {
heightMin: 32px;
}
groupStickersSubTitleHeight: 36px;
sendMediaPreviewSize: 308px;

View file

@ -22,6 +22,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "lang/lang_keys.h"
#include "storage/localstorage.h"
#include "storage/storage_media_prepare.h"
#include "mainwidget.h"
#include "history/history_media_types.h"
#include "core/file_utilities.h"
@ -29,6 +30,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "ui/widgets/buttons.h"
#include "ui/widgets/input_fields.h"
#include "ui/empty_userpic.h"
#include "ui/grouped_layout.h"
#include "styles/style_history.h"
#include "styles/style_boxes.h"
#include "media/media_clip_reader.h"
@ -38,36 +40,38 @@ namespace {
constexpr auto kMinPreviewWidth = 20;
bool ValidatePhotoDimensions(int width, int height) {
return (width > 0) && (height > 0) && (width < 20 * height) && (height < 20 * width);
}
} // namespace
SendFilesBox::SendFilesBox(QWidget*, QImage image, CompressConfirm compressed)
: _image(image)
, _compressConfirm(compressed)
, _caption(this, st::confirmCaptionArea, langFactory(lng_photo_caption)) {
_files.push_back(QString());
_list.files.push_back({ QString() });
prepareSingleFileLayout();
}
SendFilesBox::SendFilesBox(QWidget*, const QStringList &files, CompressConfirm compressed)
: _files(files)
SendFilesBox::SendFilesBox(QWidget*, Storage::PreparedList &&list, CompressConfirm compressed)
: _list(std::move(list))
, _compressConfirm(compressed)
, _caption(this, st::confirmCaptionArea, langFactory(_files.size() > 1 ? lng_photos_comment : lng_photo_caption)) {
if (_files.size() == 1) {
, _caption(
this,
st::confirmCaptionArea,
langFactory(_list.files.size() > 1
? lng_photos_comment
: lng_photo_caption)) {
if (_list.files.size() == 1) {
prepareSingleFileLayout();
}
}
void SendFilesBox::prepareSingleFileLayout() {
Expects(_files.size() == 1);
if (!_files.front().isEmpty()) {
Expects(_list.files.size() == 1);
if (!_list.files.front().path.isEmpty()) {
tryToReadSingleFile();
}
if (_image.isNull() || !ValidatePhotoDimensions(_image.width(), _image.height()) || _animated) {
if (!Storage::ValidateThumbDimensions(_image.width(), _image.height())
|| _animated) {
_compressConfirm = CompressConfirm::None;
}
@ -86,7 +90,7 @@ void SendFilesBox::prepareSingleFileLayout() {
auto maxW = 0;
auto maxH = 0;
if (_animated) {
auto limitW = st::boxWideWidth - st::boxPhotoPadding.left() - st::boxPhotoPadding.right();
auto limitW = st::sendMediaPreviewSize;
auto limitH = st::confirmMaxHeight;
maxW = qMax(image.width(), 1);
maxH = qMax(image.height(), 1);
@ -108,7 +112,7 @@ void SendFilesBox::prepareSingleFileLayout() {
if (!originalWidth || !originalHeight) {
originalWidth = originalHeight = 1;
}
_previewWidth = st::boxWideWidth - st::boxPhotoPadding.left() - st::boxPhotoPadding.right();
_previewWidth = st::sendMediaPreviewSize;
if (image.width() < _previewWidth) {
_previewWidth = qMax(image.width(), kMinPreviewWidth);
}
@ -135,20 +139,26 @@ void SendFilesBox::prepareSingleFileLayout() {
}
void SendFilesBox::prepareGifPreview() {
using namespace Media::Clip;
auto createGifPreview = [this] {
if (!_information) {
const auto &information = _list.files.front().information;
if (!information) {
return false;
}
if (auto video = base::get_if<FileLoadTask::Video>(&_information->media)) {
if (const auto video = base::get_if<FileMediaInformation::Video>(
&information->media)) {
return video->isGifv;
}
// Plain old .gif animation.
return _animated;
};
if (createGifPreview()) {
_gifPreview = Media::Clip::MakeReader(_files.front(), [this](Media::Clip::Notification notification) {
const auto callback = [this](Notification notification) {
clipCallback(notification);
});
};
_gifPreview = Media::Clip::MakeReader(
_list.files.front().path,
callback);
if (_gifPreview) _gifPreview->setAutoplay();
}
}
@ -178,7 +188,8 @@ void SendFilesBox::clipCallback(Media::Clip::Notification notification) {
}
void SendFilesBox::prepareDocumentLayout() {
auto filepath = _files.front();
const auto &file = _list.files.front();
const auto filepath = file.path;
if (filepath.isEmpty()) {
auto filename = filedialogDefaultName(qsl("image"), qsl(".png"), QString(), true);
_nameText.setText(st::semiboldTextStyle, filename, _textNameOptions);
@ -192,15 +203,16 @@ void SendFilesBox::prepareDocumentLayout() {
auto songTitle = QString();
auto songPerformer = QString();
if (_information) {
if (auto song = base::get_if<FileLoadTask::Song>(&_information->media)) {
if (file.information) {
if (const auto song = base::get_if<FileMediaInformation::Song>(
&file.information->media)) {
songTitle = song->title;
songPerformer = song->performer;
_fileIsAudio = true;
}
}
auto nameString = DocumentData::ComposeNameString(
const auto nameString = DocumentData::ComposeNameString(
filename,
songTitle,
songPerformer);
@ -216,13 +228,21 @@ void SendFilesBox::prepareDocumentLayout() {
}
void SendFilesBox::tryToReadSingleFile() {
auto filepath = _files.front();
auto &file = _list.files.front();
auto filepath = file.path;
auto filemime = mimeTypeForFile(QFileInfo(filepath)).name();
_information = FileLoadTask::ReadMediaInformation(_files.front(), QByteArray(), filemime);
if (auto image = base::get_if<FileLoadTask::Image>(&_information->media)) {
if (!file.information) {
file.information = FileLoadTask::ReadMediaInformation(
filepath,
QByteArray(),
filemime);
}
if (const auto image = base::get_if<FileMediaInformation::Image>(
&file.information->media)) {
_image = image->data;
_animated = image->animated;
} else if (auto video = base::get_if<FileLoadTask::Video>(&_information->media)) {
} else if (const auto video = base::get_if<FileMediaInformation::Video>(
&file.information->media)) {
_image = video->thumbnail;
_animated = true;
}
@ -244,25 +264,34 @@ SendFilesBox::SendFilesBox(QWidget*, const QString &phone, const QString &firstn
void SendFilesBox::prepare() {
Expects(controller() != nullptr);
if (_files.size() > 1) {
if (_list.files.size() > 1) {
updateTitleText();
}
_send = addButton(langFactory(lng_send_button), [this] { onSend(); });
_send = addButton(langFactory(lng_send_button), [this] { send(); });
addButton(langFactory(lng_cancel), [this] { closeBox(); });
if (_compressConfirm != CompressConfirm::None) {
auto compressed = (_compressConfirm == CompressConfirm::Auto) ? cCompressPastedImage() : (_compressConfirm == CompressConfirm::Yes);
auto text = lng_send_images_compress(lt_count, _files.size());
auto text = lng_send_images_compress(lt_count, _list.files.size());
_compressed.create(this, text, compressed, st::defaultBoxCheckbox);
subscribe(_compressed->checkedChanged, [this](bool checked) { onCompressedChange(); });
subscribe(_compressed->checkedChanged, [this](bool checked) {
compressedChange();
});
}
if (_caption) {
_caption->setMaxLength(MaxPhotoCaption);
_caption->setCtrlEnterSubmit(Ui::CtrlEnterSubmit::Both);
connect(_caption, SIGNAL(resized()), this, SLOT(onCaptionResized()));
connect(_caption, SIGNAL(submitted(bool)), this, SLOT(onSend(bool)));
connect(_caption, SIGNAL(cancelled()), this, SLOT(onClose()));
connect(_caption, &Ui::InputArea::resized, this, [this] {
captionResized();
});
connect(_caption, &Ui::InputArea::submitted, this, [this](
bool ctrlShiftEnter) {
send(ctrlShiftEnter);
});
connect(_caption, &Ui::InputArea::cancelled, this, [this] {
closeBox();
});
}
subscribe(boxClosing, [this] {
if (!_confirmed && _cancelledCallback) {
@ -278,26 +307,32 @@ base::lambda<QString()> SendFilesBox::getSendButtonText() const {
if (!_contactPhone.isEmpty()) {
return langFactory(lng_send_button);
} else if (_compressed && _compressed->checked()) {
return [count = _files.size()] { return lng_send_photos(lt_count, count); };
return [count = _list.files.size()] {
return lng_send_photos(lt_count, count);
};
}
return [count = _files.size()] { return lng_send_files(lt_count, count); };
return [count = _list.files.size()] {
return lng_send_files(lt_count, count);
};
}
void SendFilesBox::onCompressedChange() {
void SendFilesBox::compressedChange() {
setInnerFocus();
_send->setText(getSendButtonText());
updateButtonsGeometry();
updateControlsGeometry();
}
void SendFilesBox::onCaptionResized() {
void SendFilesBox::captionResized() {
updateBoxSize();
updateControlsGeometry();
update();
}
void SendFilesBox::updateTitleText() {
_titleText = (_compressConfirm == CompressConfirm::None) ? lng_send_files_selected(lt_count, _files.size()) : lng_send_images_selected(lt_count, _files.size());
_titleText = (_compressConfirm == CompressConfirm::None)
? lng_send_files_selected(lt_count, _list.files.size())
: lng_send_images_selected(lt_count, _list.files.size());
update();
}
@ -307,7 +342,7 @@ void SendFilesBox::updateBoxSize() {
newHeight += st::boxPhotoPadding.top() + _previewHeight;
} else if (!_fileThumb.isNull()) {
newHeight += st::boxPhotoPadding.top() + st::msgFileThumbPadding.top() + st::msgFileThumbSize + st::msgFileThumbPadding.bottom();
} else if (_files.size() > 1) {
} else if (_list.files.size() > 1) {
newHeight += 0;
} else {
newHeight += st::boxPhotoPadding.top() + st::msgFilePadding.top() + st::msgFileSize + st::msgFilePadding.bottom();
@ -323,7 +358,11 @@ void SendFilesBox::updateBoxSize() {
void SendFilesBox::keyPressEvent(QKeyEvent *e) {
if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) {
onSend((e->modifiers().testFlag(Qt::ControlModifier) || e->modifiers().testFlag(Qt::MetaModifier)) && e->modifiers().testFlag(Qt::ShiftModifier));
const auto modifiers = e->modifiers();
const auto ctrl = modifiers.testFlag(Qt::ControlModifier)
|| modifiers.testFlag(Qt::MetaModifier);
const auto shift = modifiers.testFlag(Qt::ShiftModifier);
send(ctrl && shift);
} else {
BoxContent::keyPressEvent(e);
}
@ -368,7 +407,7 @@ void SendFilesBox::paintEvent(QPaintEvent *e) {
auto icon = &st::historyFileInPlay;
icon->paintInCenter(p, inner);
}
} else if (_files.size() < 2) {
} else if (_list.files.size() < 2) {
auto w = width() - st::boxPhotoPadding.left() - st::boxPhotoPadding.right();
auto h = _fileThumb.isNull() ? (st::msgFilePadding.top() + st::msgFileSize + st::msgFilePadding.bottom()) : (st::msgFileThumbPadding.top() + st::msgFileThumbSize + st::msgFileThumbPadding.bottom());
auto nameleft = 0, nametop = 0, nameright = 0, statustop = 0, linktop = 0;
@ -428,7 +467,7 @@ void SendFilesBox::resizeEvent(QResizeEvent *e) {
void SendFilesBox::updateControlsGeometry() {
auto bottom = height();
if (_caption) {
_caption->resize(st::boxWideWidth - st::boxPhotoPadding.left() - st::boxPhotoPadding.right(), _caption->height());
_caption->resize(st::sendMediaPreviewSize, _caption->height());
_caption->moveToLeft(st::boxPhotoPadding.left(), bottom - _caption->height());
bottom -= st::boxPhotoCaptionSkip + _caption->height();
}
@ -446,7 +485,7 @@ void SendFilesBox::setInnerFocus() {
}
}
void SendFilesBox::onSend(bool ctrlShiftEnter) {
void SendFilesBox::send(bool ctrlShiftEnter) {
if (_compressed && _compressConfirm == CompressConfirm::Auto && _compressed->checked() != cCompressPastedImage()) {
cSetCompressPastedImage(_compressed->checked());
Local::writeUserSettings();
@ -455,13 +494,201 @@ void SendFilesBox::onSend(bool ctrlShiftEnter) {
if (_confirmedCallback) {
auto compressed = _compressed ? _compressed->checked() : false;
auto caption = _caption ? TextUtilities::PrepareForSending(_caption->getLastText(), TextUtilities::PrepareTextOption::CheckLinks) : QString();
_confirmedCallback(_files, _animated ? QImage() : _image, std::move(_information), compressed, caption, ctrlShiftEnter);
_confirmedCallback(
std::move(_list),
_animated ? QImage() : _image,
compressed,
caption,
ctrlShiftEnter);
}
closeBox();
}
SendFilesBox::~SendFilesBox() = default;
struct SendAlbumBox::Thumb {
Ui::GroupMediaLayout layout;
QPixmap image;
};
SendAlbumBox::SendAlbumBox(QWidget*, Storage::PreparedList &&list)
: _list(std::move(list))
, _caption(
this,
st::confirmCaptionArea,
langFactory(_list.files.size() > 1
? lng_photos_comment
: lng_photo_caption)) {
}
void SendAlbumBox::prepare() {
Expects(controller() != nullptr);
prepareThumbs();
addButton(langFactory(lng_send_button), [this] { send(); });
addButton(langFactory(lng_cancel), [this] { closeBox(); });
if (_caption) {
_caption->setMaxLength(MaxPhotoCaption);
_caption->setCtrlEnterSubmit(Ui::CtrlEnterSubmit::Both);
connect(_caption, &Ui::InputArea::resized, this, [this] {
captionResized();
});
connect(_caption, &Ui::InputArea::submitted, this, [this](
bool ctrlShiftEnter) {
send(ctrlShiftEnter);
});
connect(_caption, &Ui::InputArea::cancelled, this, [this] {
closeBox();
});
}
subscribe(boxClosing, [this] {
if (!_confirmed && _cancelledCallback) {
_cancelledCallback();
}
});
updateButtonsGeometry();
updateBoxSize();
}
void SendAlbumBox::prepareThumbs() {
auto sizes = ranges::view::all(
_list.files
) | ranges::view::transform([](const Storage::PreparedFile &file) {
return file.preview.size() / cIntRetinaFactor();
}) | ranges::to_vector;
const auto count = int(sizes.size());
const auto layout = Ui::LayoutMediaGroup(
sizes,
st::sendMediaPreviewSize,
st::historyGroupWidthMin / 2,
st::historyGroupSkip / 2);
Assert(layout.size() == count);
_thumbs.reserve(count);
for (auto i = 0; i != count; ++i) {
_thumbs.push_back(prepareThumb(_list.files[i].preview, layout[i]));
const auto &geometry = layout[i].geometry;
accumulate_max(_thumbsHeight, geometry.y() + geometry.height());
}
}
SendAlbumBox::Thumb SendAlbumBox::prepareThumb(
const QImage &preview,
const Ui::GroupMediaLayout &layout) const {
auto result = Thumb();
result.layout = layout;
const auto width = layout.geometry.width();
const auto height = layout.geometry.height();
const auto corners = Ui::GetCornersFromSides(layout.sides);
using Option = Images::Option;
const auto options = Option::Smooth
| Option::RoundedLarge
| ((corners & RectPart::TopLeft) ? Option::RoundedTopLeft : Option::None)
| ((corners & RectPart::TopRight) ? Option::RoundedTopRight : Option::None)
| ((corners & RectPart::BottomLeft) ? Option::RoundedBottomLeft : Option::None)
| ((corners & RectPart::BottomRight) ? Option::RoundedBottomRight : Option::None);
const auto pixSize = Ui::GetImageScaleSizeForGeometry(
{ preview.width(), preview.height() },
{ width, height });
const auto pixWidth = pixSize.width() * cIntRetinaFactor();
const auto pixHeight = pixSize.height() * cIntRetinaFactor();
result.image = App::pixmapFromImageInPlace(Images::prepare(
preview,
pixWidth,
pixHeight,
options,
width,
height));
return result;
}
void SendAlbumBox::captionResized() {
updateBoxSize();
updateControlsGeometry();
update();
}
void SendAlbumBox::updateBoxSize() {
auto newHeight = st::boxPhotoPadding.top() + _thumbsHeight;
if (_caption) {
newHeight += st::boxPhotoCaptionSkip + _caption->height();
}
setDimensions(st::boxWideWidth, newHeight);
}
void SendAlbumBox::keyPressEvent(QKeyEvent *e) {
if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) {
const auto modifiers = e->modifiers();
const auto ctrl = modifiers.testFlag(Qt::ControlModifier)
|| modifiers.testFlag(Qt::MetaModifier);
const auto shift = modifiers.testFlag(Qt::ShiftModifier);
send(ctrl && shift);
} else {
BoxContent::keyPressEvent(e);
}
}
void SendAlbumBox::paintEvent(QPaintEvent *e) {
BoxContent::paintEvent(e);
Painter p(this);
const auto left = (st::boxWideWidth - st::sendMediaPreviewSize) / 2;
const auto top = st::boxPhotoPadding.top();
for (const auto &thumb : _thumbs) {
p.drawPixmap(
left + thumb.layout.geometry.x(),
top + thumb.layout.geometry.y(),
thumb.image);
}
}
void SendAlbumBox::resizeEvent(QResizeEvent *e) {
BoxContent::resizeEvent(e);
updateControlsGeometry();
}
void SendAlbumBox::updateControlsGeometry() {
auto bottom = height();
if (_caption) {
_caption->resize(st::sendMediaPreviewSize, _caption->height());
_caption->moveToLeft(st::boxPhotoPadding.left(), bottom - _caption->height());
bottom -= st::boxPhotoCaptionSkip + _caption->height();
}
}
void SendAlbumBox::setInnerFocus() {
if (!_caption || _caption->isHidden()) {
setFocus();
} else {
_caption->setFocusFast();
}
}
void SendAlbumBox::send(bool ctrlShiftEnter) {
_confirmed = true;
if (_confirmedCallback) {
auto caption = _caption
? TextUtilities::PrepareForSending(
_caption->getLastText(),
TextUtilities::PrepareTextOption::CheckLinks)
: QString();
_confirmedCallback(
std::move(_list),
caption,
ctrlShiftEnter);
}
closeBox();
}
SendAlbumBox::~SendAlbumBox() = default;
EditCaptionBox::EditCaptionBox(QWidget*, HistoryMedia *media, FullMsgId msgId) : _msgId(msgId) {
Expects(media->canEditCaption());
@ -545,7 +772,7 @@ EditCaptionBox::EditCaptionBox(QWidget*, HistoryMedia *media, FullMsgId msgId) :
} else {
int32 maxW = 0, maxH = 0;
if (_animated) {
int32 limitW = st::boxWideWidth - st::boxPhotoPadding.left() - st::boxPhotoPadding.right();
int32 limitW = st::sendMediaPreviewSize;
int32 limitH = st::confirmMaxHeight;
maxW = qMax(dimensions.width(), 1);
maxH = qMax(dimensions.height(), 1);
@ -571,7 +798,7 @@ EditCaptionBox::EditCaptionBox(QWidget*, HistoryMedia *media, FullMsgId msgId) :
if (!tw || !th) {
tw = th = 1;
}
_thumbw = st::boxWideWidth - st::boxPhotoPadding.left() - st::boxPhotoPadding.right();
_thumbw = st::sendMediaPreviewSize;
if (_thumb.width() < _thumbw) {
_thumbw = (_thumb.width() > 20) ? _thumb.width() : 20;
}
@ -634,20 +861,24 @@ void EditCaptionBox::clipCallback(Media::Clip::Notification notification) {
}
void EditCaptionBox::prepare() {
addButton(langFactory(lng_settings_save), [this] { onSave(); });
addButton(langFactory(lng_settings_save), [this] { save(); });
addButton(langFactory(lng_cancel), [this] { closeBox(); });
updateBoxSize();
connect(_field, SIGNAL(submitted(bool)), this, SLOT(onSave(bool)));
connect(_field, SIGNAL(cancelled()), this, SLOT(onClose()));
connect(_field, SIGNAL(resized()), this, SLOT(onCaptionResized()));
connect(_field, &Ui::InputArea::submitted, this, [this] { save(); });
connect(_field, &Ui::InputArea::cancelled, this, [this] {
closeBox();
});
connect(_field, &Ui::InputArea::resized, this, [this] {
captionResized();
});
auto cursor = _field->textCursor();
cursor.movePosition(QTextCursor::End);
_field->setTextCursor(cursor);
}
void EditCaptionBox::onCaptionResized() {
void EditCaptionBox::captionResized() {
updateBoxSize();
resizeEvent(0);
update();
@ -767,7 +998,7 @@ void EditCaptionBox::paintEvent(QPaintEvent *e) {
void EditCaptionBox::resizeEvent(QResizeEvent *e) {
BoxContent::resizeEvent(e);
_field->resize(st::boxWideWidth - st::boxPhotoPadding.left() - st::boxPhotoPadding.right(), _field->height());
_field->resize(st::sendMediaPreviewSize, _field->height());
_field->moveToLeft(st::boxPhotoPadding.left(), height() - st::normalFont->height - errorTopSkip() - _field->height());
}
@ -775,7 +1006,7 @@ void EditCaptionBox::setInnerFocus() {
_field->setFocusFast();
}
void EditCaptionBox::onSave(bool ctrlShiftEnter) {
void EditCaptionBox::save() {
if (_saveRequestId) return;
auto item = App::histItemById(_msgId);

View file

@ -22,23 +22,23 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "boxes/abstract_box.h"
#include "storage/localimageloader.h"
#include "storage/storage_media_prepare.h"
namespace Ui {
class Checkbox;
class RoundButton;
class InputArea;
class EmptyUserpic;
struct GroupMediaLayout;
} // namespace Ui
class SendFilesBox : public BoxContent {
Q_OBJECT
public:
SendFilesBox(QWidget*, QImage image, CompressConfirm compressed);
SendFilesBox(QWidget*, const QStringList &files, CompressConfirm compressed);
SendFilesBox(QWidget*, Storage::PreparedList &&list, CompressConfirm compressed);
SendFilesBox(QWidget*, const QString &phone, const QString &firstname, const QString &lastname);
void setConfirmedCallback(base::lambda<void(const QStringList &files, const QImage &image, std::unique_ptr<FileLoadTask::MediaInformation> information, bool compressed, const QString &caption, bool ctrlShiftEnter)> callback) {
void setConfirmedCallback(base::lambda<void(Storage::PreparedList &&list, const QImage &image, bool compressed, const QString &caption, bool ctrlShiftEnter)> callback) {
_confirmedCallback = std::move(callback);
}
void setCancelledCallback(base::lambda<void()> callback) {
@ -55,14 +55,6 @@ protected:
void paintEvent(QPaintEvent *e) override;
void resizeEvent(QResizeEvent *e) override;
private slots:
void onCompressedChange();
void onSend(bool ctrlShiftEnter = false);
void onCaptionResized();
void onClose() {
closeBox();
}
private:
void prepareSingleFileLayout();
void prepareDocumentLayout();
@ -70,15 +62,18 @@ private:
void prepareGifPreview();
void clipCallback(Media::Clip::Notification notification);
void send(bool ctrlShiftEnter = false);
void captionResized();
void compressedChange();
void updateTitleText();
void updateBoxSize();
void updateControlsGeometry();
base::lambda<QString()> getSendButtonText() const;
QString _titleText;
QStringList _files;
Storage::PreparedList _list;
QImage _image;
std::unique_ptr<FileLoadTask::MediaInformation> _information;
CompressConfirm _compressConfirm = CompressConfirm::None;
bool _animated = false;
@ -101,7 +96,7 @@ private:
QString _contactLastName;
std::unique_ptr<Ui::EmptyUserpic> _contactPhotoEmpty;
base::lambda<void(const QStringList &files, const QImage &image, std::unique_ptr<FileLoadTask::MediaInformation> information, bool compressed, const QString &caption, bool ctrlShiftEnter)> _confirmedCallback;
base::lambda<void(Storage::PreparedList &&list, const QImage &image, bool compressed, const QString &caption, bool ctrlShiftEnter)> _confirmedCallback;
base::lambda<void()> _cancelledCallback;
bool _confirmed = false;
@ -112,19 +107,58 @@ private:
};
class EditCaptionBox : public BoxContent, public RPCSender {
Q_OBJECT
class SendAlbumBox : public BoxContent {
public:
SendAlbumBox(QWidget*, Storage::PreparedList &&list);
void setConfirmedCallback(base::lambda<void(Storage::PreparedList &&list, const QString &caption, bool ctrlShiftEnter)> callback) {
_confirmedCallback = std::move(callback);
}
void setCancelledCallback(base::lambda<void()> callback) {
_cancelledCallback = std::move(callback);
}
~SendAlbumBox();
protected:
void prepare() override;
void setInnerFocus() override;
void keyPressEvent(QKeyEvent *e) override;
void paintEvent(QPaintEvent *e) override;
void resizeEvent(QResizeEvent *e) override;
private:
struct Thumb;
void prepareThumbs();
Thumb prepareThumb(
const QImage &preview,
const Ui::GroupMediaLayout &layout) const;
void send(bool ctrlShiftEnter = false);
void captionResized();
void updateBoxSize();
void updateControlsGeometry();
Storage::PreparedList _list;
std::vector<Thumb> _thumbs;
int _thumbsHeight = 0;
base::lambda<void(Storage::PreparedList &&list, const QString &caption, bool ctrlShiftEnter)> _confirmedCallback;
base::lambda<void()> _cancelledCallback;
bool _confirmed = false;
object_ptr<Ui::InputArea> _caption = { nullptr };
};
class EditCaptionBox : public BoxContent, public RPCSender {
public:
EditCaptionBox(QWidget*, HistoryMedia *media, FullMsgId msgId);
public slots:
void onCaptionResized();
void onSave(bool ctrlShiftEnter = false);
void onClose() {
closeBox();
}
protected:
void prepare() override;
void setInnerFocus() override;
@ -137,6 +171,9 @@ private:
void prepareGifPreview(DocumentData *document);
void clipCallback(Media::Clip::Notification notification);
void save();
void captionResized();
void saveDone(const MTPUpdates &updates);
bool saveFail(const RPCError &error);

View file

@ -285,8 +285,6 @@ enum {
DialogsFirstLoad = 20, // first dialogs part size requested
DialogsPerPage = 500, // next dialogs part size
FileLoaderQueueStopTimeout = 5000,
UseBigFilesFrom = 10 * 1024 * 1024, // mtp big files methods used for files greater than 10mb
UploadPartSize = 32 * 1024, // 32kb for photo

View file

@ -38,6 +38,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "window/window_controller.h"
#include "window/window_slide_animation.h"
#include "profile/profile_channel_controllers.h"
#include "storage/storage_media_prepare.h"
namespace {
@ -745,18 +746,22 @@ bool DialogsWidget::peopleFailed(const RPCError &error, mtpRequestId req) {
}
void DialogsWidget::dragEnterEvent(QDragEnterEvent *e) {
using namespace Storage;
if (App::main()->selectingPeer()) return;
const auto data = e->mimeData();
_dragInScroll = false;
_dragForward = e->mimeData()->hasFormat(qsl("application/x-td-forward-selected"));
if (!_dragForward) _dragForward = e->mimeData()->hasFormat(qsl("application/x-td-forward-pressed-link"));
if (!_dragForward) _dragForward = e->mimeData()->hasFormat(qsl("application/x-td-forward-pressed"));
if (_dragForward && Adaptive::OneColumn()) _dragForward = false;
_dragForward = Adaptive::OneColumn()
? false
: (data->hasFormat(qsl("application/x-td-forward-selected"))
|| data->hasFormat(qsl("application/x-td-forward-pressed-link"))
|| data->hasFormat(qsl("application/x-td-forward-pressed")));
if (_dragForward) {
e->setDropAction(Qt::CopyAction);
e->accept();
updateDragInScroll(_scroll->geometry().contains(e->pos()));
} else if (App::main() && App::main()->getDragState(e->mimeData()) != DragState::None) {
} else if (ComputeMimeDataState(data) != MimeDataState::None) {
e->setDropAction(Qt::CopyAction);
e->accept();
}

View file

@ -42,6 +42,9 @@ struct MessageGroupId {
explicit operator bool() const {
return value != None;
}
Underlying raw() const {
return static_cast<Underlying>(value);
}
friend inline Type value_ordering_helper(MessageGroupId value) {
return value.value;

View file

@ -28,26 +28,6 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "ui/grouped_layout.h"
#include "styles/style_history.h"
namespace {
RectParts GetCornersFromSides(RectParts sides) {
const auto convert = [&](
RectPart side1,
RectPart side2,
RectPart corner) {
return ((sides & side1) && (sides & side2))
? corner
: RectPart::None;
};
return RectPart::None
| convert(RectPart::Top, RectPart::Left, RectPart::TopLeft)
| convert(RectPart::Top, RectPart::Right, RectPart::TopRight)
| convert(RectPart::Bottom, RectPart::Left, RectPart::BottomLeft)
| convert(RectPart::Bottom, RectPart::Right, RectPart::BottomRight);
}
} // namespace
HistoryGroupedMedia::Element::Element(not_null<HistoryItem*> item)
: item(item) {
}
@ -62,6 +42,12 @@ HistoryGroupedMedia::HistoryGroupedMedia(
Ensures(result);
}
std::unique_ptr<HistoryMedia> HistoryGroupedMedia::clone(
not_null<HistoryItem*> newParent,
not_null<HistoryItem*> realParent) const {
return main()->clone(newParent, realParent);
}
void HistoryGroupedMedia::initDimensions() {
if (_caption.hasSkipBlock()) {
_caption.setSkipBlock(
@ -77,7 +63,7 @@ void HistoryGroupedMedia::initDimensions() {
sizes.push_back(media->sizeForGrouping());
}
const auto layout = Data::LayoutMediaGroup(
const auto layout = Ui::LayoutMediaGroup(
sizes,
st::historyGroupWidthMax,
st::historyGroupWidthMin,
@ -171,7 +157,7 @@ void HistoryGroupedMedia::draw(
: IsGroupItemSelection(selection, i)
? FullSelection
: TextSelection();
auto corners = GetCornersFromSides(element.sides);
auto corners = Ui::GetCornersFromSides(element.sides);
if (!isBubbleTop()) {
corners &= ~(RectPart::TopLeft | RectPart::TopRight);
}
@ -409,6 +395,23 @@ Storage::SharedMediaTypesMask HistoryGroupedMedia::sharedMediaTypes() const {
return main()->sharedMediaTypes();
}
void HistoryGroupedMedia::updateSentMedia(const MTPMessageMedia &media) {
return main()->updateSentMedia(media);
}
bool HistoryGroupedMedia::needReSetInlineResultMedia(
const MTPMessageMedia &media) {
return main()->needReSetInlineResultMedia(media);
}
PhotoData *HistoryGroupedMedia::getPhoto() const {
return main()->getPhoto();
}
DocumentData *HistoryGroupedMedia::getDocument() const {
return main()->getDocument();
}
HistoryMessageEdited *HistoryGroupedMedia::displayedEditBadge() const {
if (!_caption.isEmpty()) {
return _elements.front().item->Get<HistoryMessageEdited>();

View file

@ -34,14 +34,14 @@ public:
return MediaTypeGrouped;
}
std::unique_ptr<HistoryMedia> clone(
not_null<HistoryItem*> newParent,
not_null<HistoryItem*> realParent) const override {
return main()->clone(newParent, realParent);
}
not_null<HistoryItem*> newParent,
not_null<HistoryItem*> realParent) const override;
void initDimensions() override;
int resizeGetHeight(int width) override;
void refreshParentId(not_null<HistoryItem*> realParent) override;
void updateSentMedia(const MTPMessageMedia &media) override;
bool needReSetInlineResultMedia(const MTPMessageMedia &media) override;
void draw(
Painter &p,
@ -66,12 +66,8 @@ public:
return !_caption.isEmpty();
}
PhotoData *getPhoto() const override {
return main()->getPhoto();
}
DocumentData *getDocument() const override {
return main()->getDocument();
}
PhotoData *getPhoto() const override;
DocumentData *getDocument() const override;
QString notificationText() const override;
QString inDialogsText() const override;

View file

@ -40,6 +40,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "styles/style_history.h"
#include "calls/calls_instance.h"
#include "ui/empty_userpic.h"
#include "ui/grouped_layout.h"
namespace {
@ -118,33 +119,6 @@ int32 gifMaxStatusWidth(DocumentData *document) {
return result;
}
QSize CountPixSizeForSize(QSize original, QSize geometry) {
const auto width = geometry.width();
const auto height = geometry.height();
auto tw = original.width();
auto th = original.height();
if (tw * height > th * width) {
if (th > height || tw * height < 2 * th * width) {
tw = (height * tw) / th;
th = height;
} else if (tw < width) {
th = (width * th) / tw;
tw = width;
}
} else {
if (tw > width || th * width < 2 * tw * height) {
th = (width * th) / tw;
tw = width;
} else if (tw > 0 && th < height) {
tw = (height * tw) / th;
th = height;
}
}
if (tw < 1) tw = 1;
if (th < 1) th = 1;
return { tw, th };
}
} // namespace
void HistoryInitMedia() {
@ -726,7 +700,7 @@ void HistoryPhoto::validateGroupedCache(
const auto originalWidth = convertScale(_data->full->width());
const auto originalHeight = convertScale(_data->full->height());
const auto pixSize = CountPixSizeForSize(
const auto pixSize = Ui::GetImageScaleSizeForGeometry(
{ originalWidth, originalHeight },
{ width, height });
const auto pixWidth = pixSize.width() * cIntRetinaFactor();
@ -1252,7 +1226,7 @@ void HistoryVideo::validateGroupedCache(
const auto originalWidth = convertScale(_data->thumb->width());
const auto originalHeight = convertScale(_data->thumb->height());
const auto pixSize = CountPixSizeForSize(
const auto pixSize = Ui::GetImageScaleSizeForGeometry(
{ originalWidth, originalHeight },
{ width, height });
const auto pixWidth = pixSize.width();

View file

@ -61,17 +61,18 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "mainwindow.h"
#include "passcodewidget.h"
#include "mainwindow.h"
#include "storage/localimageloader.h"
#include "storage/localstorage.h"
#include "storage/file_upload.h"
#include "storage/storage_media_prepare.h"
#include "media/media_audio.h"
#include "media/media_audio_capture.h"
#include "media/player/media_player_instance.h"
#include "storage/localstorage.h"
#include "apiwrap.h"
#include "history/history_top_bar_widget.h"
#include "observer_peer.h"
#include "base/qthelp_regex.h"
#include "ui/widgets/popup_menu.h"
#include "platform/platform_file_utilities.h"
#include "auth_session.h"
#include "window/themes/window_theme.h"
#include "window/notifications_manager.h"
@ -104,36 +105,6 @@ ApiWrap::RequestMessageDataCallback replyEditMessageDataCallback() {
};
}
MTPVector<MTPDocumentAttribute> composeDocumentAttributes(DocumentData *document) {
auto filenameAttribute = MTP_documentAttributeFilename(
MTP_string(document->filename()));
auto attributes = QVector<MTPDocumentAttribute>(1, filenameAttribute);
if (document->dimensions.width() > 0 && document->dimensions.height() > 0) {
int32 duration = document->duration();
if (duration >= 0) {
auto flags = MTPDdocumentAttributeVideo::Flags(0);
if (document->isVideoMessage()) {
flags |= MTPDdocumentAttributeVideo::Flag::f_round_message;
}
attributes.push_back(MTP_documentAttributeVideo(MTP_flags(flags), MTP_int(duration), MTP_int(document->dimensions.width()), MTP_int(document->dimensions.height())));
} else {
attributes.push_back(MTP_documentAttributeImageSize(MTP_int(document->dimensions.width()), MTP_int(document->dimensions.height())));
}
}
if (document->type == AnimatedDocument) {
attributes.push_back(MTP_documentAttributeAnimated());
} else if (document->type == StickerDocument && document->sticker()) {
attributes.push_back(MTP_documentAttributeSticker(MTP_flags(0), MTP_string(document->sticker()->alt), document->sticker()->set, MTPMaskCoords()));
} else if (const auto song = document->song()) {
auto flags = MTPDdocumentAttributeAudio::Flag::f_title | MTPDdocumentAttributeAudio::Flag::f_performer;
attributes.push_back(MTP_documentAttributeAudio(MTP_flags(flags), MTP_int(song->duration), MTP_string(song->title), MTP_string(song->performer), MTPstring()));
} else if (const auto voice = document->voice()) {
auto flags = MTPDdocumentAttributeAudio::Flag::f_voice | MTPDdocumentAttributeAudio::Flag::f_waveform;
attributes.push_back(MTP_documentAttributeAudio(MTP_flags(flags), MTP_int(voice->duration), MTPstring(), MTPstring(), MTP_bytes(documentWaveformEncode5bit(voice->waveform))));
}
return MTP_vector<MTPDocumentAttribute>(attributes);
}
} // namespace
ReportSpamPanel::ReportSpamPanel(QWidget *parent) : TWidget(parent),
@ -448,9 +419,9 @@ HistoryWidget::HistoryWidget(QWidget *parent, not_null<Window::Controller*> cont
, _kbScroll(this, st::botKbScroll)
, _tabbedPanel(this, controller)
, _tabbedSelector(_tabbedPanel->getSelector())
, _attachDragState(DragState::None)
, _attachDragDocument(this)
, _attachDragPhoto(this)
, _fileLoader(this, FileLoaderQueueStopTimeout)
, _sendActionStopTimer([this] { cancelTypingAction(); })
, _topShadow(this) {
setAcceptDrops(true);
@ -1294,14 +1265,17 @@ void HistoryWidget::onRecordError() {
stopRecording(false);
}
void HistoryWidget::onRecordDone(QByteArray result, VoiceWaveform waveform, qint32 samples) {
void HistoryWidget::onRecordDone(
QByteArray result,
VoiceWaveform waveform,
qint32 samples) {
if (!canWriteMessage() || result.isEmpty()) return;
App::wnd()->activateWindow();
auto duration = samples / Media::Player::kDefaultFrequency;
auto to = FileLoadTo(_peer->id, _peer->notifySilentPosts(), replyToId());
auto caption = QString();
_fileLoader.addTask(std::make_unique<FileLoadTask>(result, duration, waveform, to, caption));
const auto duration = samples / Media::Player::kDefaultFrequency;
auto options = ApiWrap::SendOptions(_history);
options.replyTo = replyToId();
Auth().api().sendVoiceMessage(result, waveform, duration, options);
}
void HistoryWidget::onRecordUpdate(quint16 level, qint32 samples) {
@ -3129,19 +3103,21 @@ void HistoryWidget::chooseAttach() {
auto animated = false;
auto image = App::readImage(result.remoteContent, nullptr, false, &animated);
if (!image.isNull() && !animated) {
confirmSendingFiles(image, result.remoteContent);
confirmSendingFiles(
image,
result.remoteContent,
CompressConfirm::Auto);
} else {
uploadFile(result.remoteContent, SendMediaType::File);
}
} else {
auto lists = getSendingFilesLists(result.paths);
if (lists.allFilesForCompress) {
confirmSendingFiles(lists);
} else {
validateSendingFiles(lists, [this](const QStringList &files) {
uploadFiles(files, SendMediaType::File);
return true;
});
auto list = Storage::PrepareMediaList(
result.paths,
st::sendMediaPreviewSize);
if (list.allFilesForCompress) {
confirmSendingFiles(std::move(list), CompressConfirm::Auto);
} else if (!showSendingFilesError(list)) {
uploadFiles(std::move(list), SendMediaType::File);
}
}
}));
@ -3159,25 +3135,25 @@ void HistoryWidget::sendButtonClicked() {
void HistoryWidget::dragEnterEvent(QDragEnterEvent *e) {
if (!_history || !_canSendMessages) return;
_attachDrag = getDragState(e->mimeData());
_attachDragState = Storage::ComputeMimeDataState(e->mimeData());
updateDragAreas();
if (_attachDrag != DragState::None) {
if (_attachDragState != DragState::None) {
e->setDropAction(Qt::IgnoreAction);
e->accept();
}
}
void HistoryWidget::dragLeaveEvent(QDragLeaveEvent *e) {
if (_attachDrag != DragState::None || !_attachDragPhoto->isHidden() || !_attachDragDocument->isHidden()) {
_attachDrag = DragState::None;
if (_attachDragState != DragState::None || !_attachDragPhoto->isHidden() || !_attachDragDocument->isHidden()) {
_attachDragState = DragState::None;
updateDragAreas();
}
}
void HistoryWidget::leaveEventHook(QEvent *e) {
if (_attachDrag != DragState::None || !_attachDragPhoto->isHidden() || !_attachDragDocument->isHidden()) {
_attachDrag = DragState::None;
if (_attachDragState != DragState::None || !_attachDragPhoto->isHidden() || !_attachDragDocument->isHidden()) {
_attachDragState = DragState::None;
updateDragAreas();
}
if (hasMouseTracking()) mouseMoveEvent(0);
@ -3246,8 +3222,8 @@ void HistoryWidget::mouseReleaseEvent(QMouseEvent *e) {
_replyForwardPressed = false;
update(0, _field->y() - st::historySendPadding - st::historyReplyHeight, width(), st::historyReplyHeight);
}
if (_attachDrag != DragState::None || !_attachDragPhoto->isHidden() || !_attachDragDocument->isHidden()) {
_attachDrag = DragState::None;
if (_attachDragState != DragState::None || !_attachDragPhoto->isHidden() || !_attachDragDocument->isHidden()) {
_attachDragState = DragState::None;
updateDragAreas();
}
if (_recording) {
@ -3477,60 +3453,11 @@ QRect HistoryWidget::rectForFloatPlayer() const {
return mapToGlobal(_scroll->geometry());
}
DragState HistoryWidget::getDragState(const QMimeData *d) {
if (!d
|| d->hasFormat(qsl("application/x-td-forward-selected"))
|| d->hasFormat(qsl("application/x-td-forward-pressed"))
|| d->hasFormat(qsl("application/x-td-forward-pressed-link"))) return DragState::None;
if (d->hasImage()) return DragState::Image;
QString uriListFormat(qsl("text/uri-list"));
if (!d->hasFormat(uriListFormat)) return DragState::None;
QStringList imgExtensions(cImgExtensions()), files;
const QList<QUrl> &urls(d->urls());
if (urls.isEmpty()) return DragState::None;
bool allAreSmallImages = true;
for (QList<QUrl>::const_iterator i = urls.cbegin(), en = urls.cend(); i != en; ++i) {
if (!i->isLocalFile()) return DragState::None;
auto file = Platform::File::UrlToLocal(*i);
QFileInfo info(file);
if (info.isDir()) return DragState::None;
quint64 s = info.size();
if (s > App::kFileSizeLimit) {
return DragState::None;
}
if (allAreSmallImages) {
if (s > App::kImageSizeLimit) {
allAreSmallImages = false;
} else {
bool foundImageExtension = false;
for (QStringList::const_iterator j = imgExtensions.cbegin(), end = imgExtensions.cend(); j != end; ++j) {
if (file.right(j->size()).toLower() == (*j).toLower()) {
foundImageExtension = true;
break;
}
}
if (!foundImageExtension) {
allAreSmallImages = false;
}
}
}
}
return allAreSmallImages ? DragState::PhotoFiles : DragState::Files;
}
void HistoryWidget::updateDragAreas() {
_field->setAcceptDrops(_attachDrag == DragState::None);
_field->setAcceptDrops(_attachDragState == DragState::None);
updateControlsGeometry();
switch (_attachDrag) {
switch (_attachDragState) {
case DragState::None:
_attachDragDocument->otherLeave();
_attachDragPhoto->otherLeave();
@ -3670,7 +3597,7 @@ bool HistoryWidget::kbWasHidden() const {
}
void HistoryWidget::dropEvent(QDropEvent *e) {
_attachDrag = DragState::None;
_attachDragState = DragState::None;
updateDragAreas();
e->acceptProposedAction();
}
@ -4006,11 +3933,20 @@ void HistoryWidget::updateFieldPlaceholder() {
}
template <typename SendCallback>
bool HistoryWidget::showSendFilesBox(object_ptr<SendFilesBox> box, const QString &insertTextOnCancel, const QString *addedComment, SendCallback callback) {
bool HistoryWidget::showSendFilesBox(
object_ptr<SendFilesBox> box,
const QString &insertTextOnCancel,
const QString *addedComment,
SendCallback callback) {
App::wnd()->activateWindow();
auto withComment = (addedComment != nullptr);
box->setConfirmedCallback(base::lambda_guarded(this, [this, withComment, sendCallback = std::move(callback)](const QStringList &files, const QImage &image, std::unique_ptr<FileLoadTask::MediaInformation> information, bool compressed, const QString &caption, bool ctrlShiftEnter) {
const auto confirmedCallback = [=, sendCallback = std::move(callback)](
Storage::PreparedList &&list,
const QImage &image,
bool compressed,
const QString &caption,
bool ctrlShiftEnter) {
if (!canWriteMessage()) return;
const auto replyTo = replyToId();
@ -4019,13 +3955,14 @@ bool HistoryWidget::showSendFilesBox(object_ptr<SendFilesBox> box, const QString
onSend(ctrlShiftEnter);
}
sendCallback(
files,
std::move(list),
image,
std::move(information),
compressed,
caption,
replyTo);
}));
};
box->setConfirmedCallback(
base::lambda_guarded(this, std::move(confirmedCallback)));
if (withComment) {
auto was = _field->getTextWithTags();
@ -4043,66 +3980,161 @@ bool HistoryWidget::showSendFilesBox(object_ptr<SendFilesBox> box, const QString
return true;
}
template <typename Callback>
bool HistoryWidget::validateSendingFiles(const SendingFilesLists &lists, Callback callback) {
if (!canWriteMessage()) return false;
bool HistoryWidget::showSendingFilesError(
const Storage::PreparedList &list) const {
App::wnd()->activateWindow();
if (!lists.nonLocalUrls.isEmpty()) {
Ui::show(Box<InformBox>(lng_send_image_empty(lt_name, lists.nonLocalUrls.front().toDisplayString())));
} else if (!lists.emptyFiles.isEmpty()) {
Ui::show(Box<InformBox>(lng_send_image_empty(lt_name, lists.emptyFiles.front())));
} else if (!lists.tooLargeFiles.isEmpty()) {
Ui::show(Box<InformBox>(lng_send_image_too_large(lt_name, lists.tooLargeFiles.front())));
} else if (!lists.filesToSend.isEmpty()) {
return callback(lists.filesToSend);
}
return false;
}
bool HistoryWidget::confirmSendingFiles(const QList<QUrl> &files, CompressConfirm compressed, const QString *addedComment) {
return confirmSendingFiles(getSendingFilesLists(files), compressed, addedComment);
}
bool HistoryWidget::confirmSendingFiles(const QStringList &files, CompressConfirm compressed, const QString *addedComment) {
return confirmSendingFiles(getSendingFilesLists(files), compressed, addedComment);
}
bool HistoryWidget::confirmSendingFiles(const SendingFilesLists &lists, CompressConfirm compressed, const QString *addedComment) {
if (auto megagroup = _peer ? _peer->asMegagroup() : nullptr) {
if (megagroup->restricted(ChannelRestriction::f_send_media)) {
Ui::show(Box<InformBox>(lang(lng_restricted_send_media)));
return false;
const auto text = [&] {
if (const auto megagroup = _peer ? _peer->asMegagroup() : nullptr) {
if (megagroup->restricted(ChannelRestriction::f_send_media)) {
return lang(lng_restricted_send_media);
}
} else if (!canWriteMessage()) {
return lang(lng_forward_send_files_cant);
}
using Error = Storage::PreparedList::Error;
switch (list.error) {
case Error::None: return QString();
case Error::EmptyFile:
case Error::Directory:
case Error::NonLocalUrl: return lng_send_image_empty(
lt_name,
list.errorData);
case Error::TooLargeFile: return lng_send_image_too_large(
lt_name,
list.errorData);
}
return lang(lng_forward_send_files_cant);
}();
if (text.isEmpty()) {
return false;
}
return validateSendingFiles(lists, [this, &lists, compressed, addedComment](const QStringList &files) {
auto insertTextOnCancel = QString();
auto sendCallback = [this](const QStringList &files, const QImage &image, std::unique_ptr<FileLoadTask::MediaInformation> information, bool compressed, const QString &caption, MsgId replyTo) {
auto type = compressed ? SendMediaType::Photo : SendMediaType::File;
uploadFilesAfterConfirmation(files, QByteArray(), image, std::move(information), type, caption);
Ui::show(Box<InformBox>(text));
return true;
}
bool HistoryWidget::confirmSendingFiles(const QStringList &files) {
return confirmSendingFiles(files, CompressConfirm::Auto, nullptr);
}
bool HistoryWidget::confirmSendingFiles(const QMimeData *data) {
return confirmSendingFiles(data, CompressConfirm::Auto, nullptr);
}
bool HistoryWidget::confirmSendingFiles(
const QList<QUrl> &files,
CompressConfirm compressed,
const QString *addedComment) {
return confirmSendingFiles(
Storage::PrepareMediaList(files, st::sendMediaPreviewSize),
compressed,
addedComment);
}
bool HistoryWidget::confirmSendingFiles(
const QStringList &files,
CompressConfirm compressed,
const QString *addedComment) {
return confirmSendingFiles(
Storage::PrepareMediaList(files, st::sendMediaPreviewSize),
compressed,
addedComment);
}
bool HistoryWidget::confirmSendingFiles(
Storage::PreparedList &&list,
CompressConfirm compressed,
const QString *addedComment) {
if (showSendingFilesError(list)) {
return false;
}
if (list.albumIsPossible) {
auto box = Ui::show(Box<SendAlbumBox>(std::move(list)));
const auto confirmedCallback = [=](
Storage::PreparedList &&list,
const QString &caption,
bool ctrlShiftEnter) {
if (!canWriteMessage()) return;
uploadFilesAfterConfirmation(
std::move(list),
QByteArray(),
QImage(),
SendMediaType::Photo,
caption,
replyToId(),
std::make_shared<SendingAlbum>());
};
auto boxCompressConfirm = compressed;
if (files.size() > 1 && !lists.allFilesForCompress) {
boxCompressConfirm = CompressConfirm::None;
}
auto box = Box<SendFilesBox>(files, boxCompressConfirm);
return showSendFilesBox(std::move(box), insertTextOnCancel, addedComment, std::move(sendCallback));
});
box->setConfirmedCallback(
base::lambda_guarded(this, std::move(confirmedCallback)));
return true;
} else {
const auto insertTextOnCancel = QString();
auto sendCallback = [this](
Storage::PreparedList &&list,
const QImage &image,
bool compressed,
const QString &caption,
MsgId replyTo) {
const auto type = compressed
? SendMediaType::Photo
: SendMediaType::File;
uploadFilesAfterConfirmation(
std::move(list),
QByteArray(),
image,
type,
caption,
replyTo);
};
const auto noCompressOption = (list.files.size() > 1)
&& !list.allFilesForCompress;
const auto boxCompressConfirm = noCompressOption
? CompressConfirm::None
: compressed;
return showSendFilesBox(
Box<SendFilesBox>(std::move(list), boxCompressConfirm),
insertTextOnCancel,
addedComment,
std::move(sendCallback));
}
}
bool HistoryWidget::confirmSendingFiles(const QImage &image, const QByteArray &content, CompressConfirm compressed, const QString &insertTextOnCancel) {
bool HistoryWidget::confirmSendingFiles(
const QImage &image,
const QByteArray &content,
CompressConfirm compressed,
const QString &insertTextOnCancel) {
if (!canWriteMessage() || image.isNull()) return false;
App::wnd()->activateWindow();
auto sendCallback = [this, content](const QStringList &files, const QImage &image, std::unique_ptr<FileLoadTask::MediaInformation> information, bool compressed, const QString &caption, MsgId replyTo) {
auto type = compressed ? SendMediaType::Photo : SendMediaType::File;
uploadFilesAfterConfirmation(files, content, image, std::move(information), type, caption);
auto sendCallback = [this, content](
Storage::PreparedList &&list,
const QImage &image,
bool compressed,
const QString &caption,
MsgId replyTo) {
const auto type = compressed
? SendMediaType::Photo
: SendMediaType::File;
uploadFilesAfterConfirmation(
std::move(list),
content,
image,
type,
caption,
replyTo);
};
auto box = Box<SendFilesBox>(image, compressed);
return showSendFilesBox(std::move(box), insertTextOnCancel, nullptr, std::move(sendCallback));
return showSendFilesBox(
Box<SendFilesBox>(image, compressed),
insertTextOnCancel,
nullptr,
std::move(sendCallback));
}
bool HistoryWidget::confirmSendingFiles(const QMimeData *data, CompressConfirm compressed, const QString &insertTextOnCancel) {
bool HistoryWidget::confirmSendingFiles(
const QMimeData *data,
CompressConfirm compressed,
const QString &insertTextOnCancel) {
if (!canWriteMessage()) {
return false;
}
@ -4119,7 +4151,11 @@ bool HistoryWidget::confirmSendingFiles(const QMimeData *data, CompressConfirm c
if (data->hasImage()) {
auto image = qvariant_cast<QImage>(data->imageData());
if (!image.isNull()) {
confirmSendingFiles(image, QByteArray(), compressed, insertTextOnCancel);
confirmSendingFiles(
image,
QByteArray(),
compressed,
insertTextOnCancel);
return true;
}
}
@ -4133,11 +4169,9 @@ bool HistoryWidget::confirmShareContact(
const QString *addedComment) {
if (!canWriteMessage()) return false;
auto box = Box<SendFilesBox>(phone, fname, lname);
auto sendCallback = [=](
const QStringList &files,
Storage::PreparedList &&list,
const QImage &image,
std::unique_ptr<FileLoadTask::MediaInformation> information,
bool compressed,
const QString &caption,
MsgId replyTo) {
@ -4145,113 +4179,79 @@ bool HistoryWidget::confirmShareContact(
options.replyTo = replyTo;
Auth().api().shareContact(phone, fname, lname, options);
};
auto insertTextOnCancel = QString();
const auto insertTextOnCancel = QString();
return showSendFilesBox(
std::move(box),
Box<SendFilesBox>(phone, fname, lname),
insertTextOnCancel,
addedComment,
std::move(sendCallback));
}
HistoryWidget::SendingFilesLists HistoryWidget::getSendingFilesLists(const QList<QUrl> &files) {
auto result = SendingFilesLists();
for_const (auto &url, files) {
if (!url.isLocalFile()) {
result.nonLocalUrls.push_back(url);
} else {
auto filepath = Platform::File::UrlToLocal(url);
getSendingLocalFileInfo(result, filepath);
}
}
return result;
}
HistoryWidget::SendingFilesLists HistoryWidget::getSendingFilesLists(const QStringList &files) {
auto result = SendingFilesLists();
for_const (auto &filepath, files) {
getSendingLocalFileInfo(result, filepath);
}
return result;
}
void HistoryWidget::getSendingLocalFileInfo(SendingFilesLists &result, const QString &filepath) {
auto hasExtensionForCompress = [](const QString &filepath) {
for_const (auto extension, cExtensionsForCompress()) {
if (filepath.right(extension.size()).compare(extension, Qt::CaseInsensitive) == 0) {
return true;
}
}
return false;
};
auto fileinfo = QFileInfo(filepath);
if (fileinfo.isDir()) {
result.directories.push_back(filepath);
} else {
auto filesize = fileinfo.size();
if (filesize <= 0) {
result.emptyFiles.push_back(filepath);
} else if (filesize > App::kFileSizeLimit) {
result.tooLargeFiles.push_back(filepath);
} else {
result.filesToSend.push_back(filepath);
if (result.allFilesForCompress) {
if (filesize > App::kImageSizeLimit || !hasExtensionForCompress(filepath)) {
result.allFilesForCompress = false;
}
}
}
}
}
void HistoryWidget::uploadFiles(const QStringList &files, SendMediaType type) {
void HistoryWidget::uploadFiles(
Storage::PreparedList &&list,
SendMediaType type) {
if (!canWriteMessage()) return;
auto caption = QString();
uploadFilesAfterConfirmation(files, QByteArray(), QImage(), nullptr, type, caption);
uploadFilesAfterConfirmation(
std::move(list),
QByteArray(),
QImage(),
type,
caption,
replyToId());
}
void HistoryWidget::uploadFilesAfterConfirmation(
const QStringList &files,
Storage::PreparedList &&list,
const QByteArray &content,
const QImage &image,
std::unique_ptr<FileLoadTask::MediaInformation> information,
SendMediaType type,
QString caption) {
QString caption,
MsgId replyTo,
std::shared_ptr<SendingAlbum> album) {
Assert(canWriteMessage());
auto to = FileLoadTo(_peer->id, _peer->notifySilentPosts(), replyToId());
if (files.size() > 1 && !caption.isEmpty()) {
auto message = MainWidget::MessageToSend(_history);
message.textWithTags = { caption, TextWithTags::Tags() };
message.replyTo = to.replyTo;
message.clearDraft = false;
App::main()->sendMessage(message);
caption = QString();
}
auto tasks = std::vector<std::unique_ptr<Task>>();
tasks.reserve(files.size());
for_const (auto &filepath, files) {
if (filepath.isEmpty() && (!image.isNull() || !content.isNull())) {
tasks.push_back(std::make_unique<FileLoadTask>(content, image, type, to, caption));
} else {
tasks.push_back(std::make_unique<FileLoadTask>(filepath, std::move(information), type, to, caption));
}
}
_fileLoader.addTasks(std::move(tasks));
auto options = ApiWrap::SendOptions(_history);
options.replyTo = replyTo;
Auth().api().sendFiles(
std::move(list),
content,
image,
type,
caption,
album,
options);
}
void HistoryWidget::uploadFile(const QByteArray &fileContent, SendMediaType type) {
void HistoryWidget::uploadFile(
const QByteArray &fileContent,
SendMediaType type) {
if (!canWriteMessage()) return;
auto to = FileLoadTo(_peer->id, _peer->notifySilentPosts(), replyToId());
auto caption = QString();
_fileLoader.addTask(std::make_unique<FileLoadTask>(fileContent, QImage(), type, to, caption));
auto options = ApiWrap::SendOptions(_history);
options.replyTo = replyToId();
Auth().api().sendFile(fileContent, type, options);
}
void HistoryWidget::sendFileConfirmed(const FileLoadResultPtr &file) {
bool lastKeyboardUsed = lastForceReplyReplied(FullMsgId(peerToChannel(file->to.peer), file->to.replyTo));
void HistoryWidget::sendFileConfirmed(
const std::shared_ptr<FileLoadResult> &file) {
const auto channelId = peerToChannel(file->to.peer);
const auto lastKeyboardUsed = lastForceReplyReplied(FullMsgId(
channelId,
file->to.replyTo));
FullMsgId newId(peerToChannel(file->to.peer), clientMsgId());
const auto newId = FullMsgId(channelId, clientMsgId());
const auto groupId = file->album ? file->album->groupId : uint64(0);
if (file->album) {
const auto proj = [](const SendingAlbum::Item &item) {
return item.taskId;
};
const auto it = ranges::find(file->album->items, file->taskId, proj);
Assert(it != file->album->items.end());
it->msgId = newId;
}
connect(&Auth().uploader(), SIGNAL(photoReady(const FullMsgId&,bool,const MTPInputFile&)), this, SLOT(onPhotoUploaded(const FullMsgId&,bool,const MTPInputFile&)), Qt::UniqueConnection);
connect(&Auth().uploader(), SIGNAL(documentReady(const FullMsgId&,bool,const MTPInputFile&)), this, SLOT(onDocumentUploaded(const FullMsgId&,bool,const MTPInputFile&)), Qt::UniqueConnection);
@ -4288,6 +4288,9 @@ void HistoryWidget::sendFileConfirmed(const FileLoadResultPtr &file) {
if (silentPost) {
flags |= MTPDmessage::Flag::f_silent;
}
if (groupId) {
flags |= MTPDmessage::Flag::f_grouped_id;
}
auto messageFromId = channelPost ? 0 : Auth().userId();
auto messagePostAuthor = channelPost ? (Auth().user()->firstName + ' ' + Auth().user()->lastName) : QString();
if (file->type == SendMediaType::Photo) {
@ -4317,7 +4320,7 @@ void HistoryWidget::sendFileConfirmed(const FileLoadResultPtr &file) {
MTP_int(1),
MTPint(),
MTP_string(messagePostAuthor),
MTPlong()),
MTP_long(groupId)),
NewMessageUnread);
} else if (file->type == SendMediaType::File) {
auto documentFlags = MTPDmessageMediaDocument::Flag::f_document | 0;
@ -4346,7 +4349,7 @@ void HistoryWidget::sendFileConfirmed(const FileLoadResultPtr &file) {
MTP_int(1),
MTPint(),
MTP_string(messagePostAuthor),
MTPlong()),
MTP_long(groupId)),
NewMessageUnread);
} else if (file->type == SendMediaType::Audio) {
if (!peer->isChannel()) {
@ -4378,7 +4381,7 @@ void HistoryWidget::sendFileConfirmed(const FileLoadResultPtr &file) {
MTP_int(1),
MTPint(),
MTP_string(messagePostAuthor),
MTPlong()),
MTP_long(groupId)),
NewMessageUnread);
}
@ -4393,90 +4396,14 @@ void HistoryWidget::onPhotoUploaded(
const FullMsgId &newId,
bool silent,
const MTPInputFile &file) {
if (auto item = App::histItemById(newId)) {
uint64 randomId = rand_value<uint64>();
App::historyRegRandom(randomId, newId);
History *hist = item->history();
MsgId replyTo = item->replyToId();
auto sendFlags = MTPmessages_SendMedia::Flags(0);
if (replyTo) {
sendFlags |= MTPmessages_SendMedia::Flag::f_reply_to_msg_id;
}
bool channelPost = hist->peer->isChannel() && !hist->peer->isMegagroup();
bool silentPost = channelPost && silent;
if (silentPost) {
sendFlags |= MTPmessages_SendMedia::Flag::f_silent;
}
auto caption = item->getMedia() ? item->getMedia()->getCaption() : TextWithEntities();
auto media = MTP_inputMediaUploadedPhoto(
MTP_flags(0),
file,
MTP_string(caption.text),
MTPVector<MTPInputDocument>(),
MTP_int(0));
hist->sendRequestId = MTP::send(
MTPmessages_SendMedia(
MTP_flags(sendFlags),
item->history()->peer->input,
MTP_int(replyTo),
media,
MTP_long(randomId),
MTPnullMarkup),
App::main()->rpcDone(&MainWidget::sentUpdatesReceived),
App::main()->rpcFail(&MainWidget::sendMessageFail),
0,
0,
hist->sendRequestId);
}
Auth().api().sendUploadedPhoto(newId, file, silent);
}
void HistoryWidget::onDocumentUploaded(
const FullMsgId &newId,
bool silent,
const MTPInputFile &file) {
if (auto item = dynamic_cast<HistoryMessage*>(App::histItemById(newId))) {
auto media = item->getMedia();
if (auto document = media ? media->getDocument() : nullptr) {
auto randomId = rand_value<uint64>();
App::historyRegRandom(randomId, newId);
auto hist = item->history();
auto replyTo = item->replyToId();
auto sendFlags = MTPmessages_SendMedia::Flags(0);
if (replyTo) {
sendFlags |= MTPmessages_SendMedia::Flag::f_reply_to_msg_id;
}
bool channelPost = hist->peer->isChannel() && !hist->peer->isMegagroup();
bool silentPost = channelPost && silent;
if (silentPost) {
sendFlags |= MTPmessages_SendMedia::Flag::f_silent;
}
auto caption = item->getMedia() ? item->getMedia()->getCaption() : TextWithEntities();
auto media = MTP_inputMediaUploadedDocument(
MTP_flags(0),
file,
MTPInputFile(),
MTP_string(document->mimeString()),
composeDocumentAttributes(document),
MTP_string(caption.text),
MTPVector<MTPInputDocument>(),
MTP_int(0));
hist->sendRequestId = MTP::send(
MTPmessages_SendMedia(
MTP_flags(sendFlags),
item->history()->peer->input,
MTP_int(replyTo),
media,
MTP_long(randomId),
MTPnullMarkup),
App::main()->rpcDone(&MainWidget::sentUpdatesReceived),
App::main()->rpcFail(&MainWidget::sendMessageFail),
0,
0,
hist->sendRequestId);
}
}
Auth().api().sendUploadedDocument(newId, file, base::none, silent);
}
void HistoryWidget::onThumbDocumentUploaded(
@ -4484,48 +4411,7 @@ void HistoryWidget::onThumbDocumentUploaded(
bool silent,
const MTPInputFile &file,
const MTPInputFile &thumb) {
if (auto item = dynamic_cast<HistoryMessage*>(App::histItemById(newId))) {
auto media = item->getMedia();
if (auto document = media ? media->getDocument() : nullptr) {
auto randomId = rand_value<uint64>();
App::historyRegRandom(randomId, newId);
auto hist = item->history();
auto replyTo = item->replyToId();
auto sendFlags = MTPmessages_SendMedia::Flags(0);
if (replyTo) {
sendFlags |= MTPmessages_SendMedia::Flag::f_reply_to_msg_id;
}
bool channelPost = hist->peer->isChannel() && !hist->peer->isMegagroup();
bool silentPost = channelPost && silent;
if (silentPost) {
sendFlags |= MTPmessages_SendMedia::Flag::f_silent;
}
auto caption = media ? media->getCaption() : TextWithEntities();
auto media = MTP_inputMediaUploadedDocument(
MTP_flags(MTPDinputMediaUploadedDocument::Flag::f_thumb),
file,
thumb,
MTP_string(document->mimeString()),
composeDocumentAttributes(document),
MTP_string(caption.text),
MTPVector<MTPInputDocument>(),
MTP_int(0));
hist->sendRequestId = MTP::send(
MTPmessages_SendMedia(
MTP_flags(sendFlags),
item->history()->peer->input,
MTP_int(replyTo),
media,
MTP_long(randomId),
MTPnullMarkup),
App::main()->rpcDone(&MainWidget::sentUpdatesReceived),
App::main()->rpcFail(&MainWidget::sendMessageFail),
0,
0,
hist->sendRequestId);
}
}
Auth().api().sendUploadedDocument(newId, file, thumb, silent);
}
void HistoryWidget::onPhotoProgress(const FullMsgId &newId) {
@ -4756,7 +4642,7 @@ void HistoryWidget::updateControlsGeometry() {
_membersDropdown->setMaxHeight(countMembersDropdownHeightMax());
}
switch (_attachDrag) {
switch (_attachDragState) {
case DragState::Files:
_attachDragDocument->resize(width() - st::dragMargin.left() - st::dragMargin.right(), height() - st::dragMargin.top() - st::dragMargin.bottom());
_attachDragDocument->move(st::dragMargin.left(), st::dragMargin.top());

View file

@ -20,7 +20,6 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#pragma once
#include "storage/localimageloader.h"
#include "ui/widgets/tooltip.h"
#include "mainwidget.h"
#include "chat_helpers/field_autocomplete.h"
@ -31,6 +30,12 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "base/flags.h"
#include "base/timer.h"
struct FileLoadResult;
struct FileMediaInformation;
struct SendingAlbum;
enum class SendMediaType;
enum class CompressConfirm;
namespace InlineBots {
namespace Layout {
class ItemBase;
@ -68,6 +73,11 @@ class TabbedSection;
class TabbedSelector;
} // namespace ChatHelpers
namespace Storage {
enum class MimeDataState;
struct PreparedList;
} // namespace Storage
class DragArea;
class SendFilesBox;
class BotKeyboard;
@ -215,16 +225,9 @@ public:
void updateFieldPlaceholder();
void updateStickersByEmoji();
bool confirmSendingFiles(const QList<QUrl> &files, CompressConfirm compressed = CompressConfirm::Auto, const QString *addedComment = nullptr);
bool confirmSendingFiles(const QStringList &files, CompressConfirm compressed = CompressConfirm::Auto, const QString *addedComment = nullptr);
bool confirmSendingFiles(const QImage &image, const QByteArray &content, CompressConfirm compressed = CompressConfirm::Auto, const QString &insertTextOnCancel = QString());
bool confirmSendingFiles(const QMimeData *data, CompressConfirm compressed = CompressConfirm::Auto, const QString &insertTextOnCancel = QString());
bool confirmShareContact(const QString &phone, const QString &fname, const QString &lname, const QString *addedComment = nullptr);
void uploadFile(const QByteArray &fileContent, SendMediaType type);
void uploadFiles(const QStringList &files, SendMediaType type);
void sendFileConfirmed(const FileLoadResultPtr &file);
bool confirmSendingFiles(const QStringList &files);
bool confirmSendingFiles(const QMimeData *data);
void sendFileConfirmed(const std::shared_ptr<FileLoadResult> &file);
void updateControlsVisibility();
void updateControlsGeometry();
@ -291,8 +294,6 @@ public:
// already shown for the passed history item.
void updateBotKeyboard(History *h = nullptr, bool force = false);
DragState getDragState(const QMimeData *d);
void fastShowAtEnd(not_null<History*> history);
void applyDraft(bool parseLinks = true, Ui::FlatTextarea::UndoHistoryAction undoHistoryAction = Ui::FlatTextarea::ClearUndoHistory);
void showHistory(const PeerId &peer, MsgId showAtMsgId, bool reload = false);
@ -452,16 +453,9 @@ private slots:
void updateField();
private:
struct SendingFilesLists {
QList<QUrl> nonLocalUrls;
QStringList directories;
QStringList emptyFiles;
QStringList tooLargeFiles;
QStringList filesToSend;
bool allFilesForCompress = true;
};
using TabbedPanel = ChatHelpers::TabbedPanel;
using TabbedSelector = ChatHelpers::TabbedSelector;
using DragState = Storage::MimeDataState;
void repaintHistoryItem(not_null<const HistoryItem*> item);
void handlePendingHistoryUpdate();
@ -502,17 +496,29 @@ private:
void historyDownAnimationFinish();
void unreadMentionsAnimationFinish();
void sendButtonClicked();
SendingFilesLists getSendingFilesLists(const QList<QUrl> &files);
SendingFilesLists getSendingFilesLists(const QStringList &files);
void getSendingLocalFileInfo(SendingFilesLists &result, const QString &filepath);
bool confirmSendingFiles(const SendingFilesLists &lists, CompressConfirm compressed = CompressConfirm::Auto, const QString *addedComment = nullptr);
template <typename Callback>
bool validateSendingFiles(const SendingFilesLists &lists, Callback callback);
bool confirmShareContact(const QString &phone, const QString &fname, const QString &lname, const QString *addedComment = nullptr);
bool confirmSendingFiles(const QList<QUrl> &files, CompressConfirm compressed, const QString *addedComment = nullptr);
bool confirmSendingFiles(const QStringList &files, CompressConfirm compressed, const QString *addedComment = nullptr);
bool confirmSendingFiles(const QImage &image, const QByteArray &content, CompressConfirm compressed, const QString &insertTextOnCancel = QString());
bool confirmSendingFiles(const QMimeData *data, CompressConfirm compressed, const QString &insertTextOnCancel = QString());
bool confirmSendingFiles(Storage::PreparedList &&list, CompressConfirm compressed, const QString *addedComment = nullptr);
bool showSendingFilesError(const Storage::PreparedList &list) const;
template <typename SendCallback>
bool showSendFilesBox(object_ptr<SendFilesBox> box, const QString &insertTextOnCancel, const QString *addedComment, SendCallback callback);
void uploadFiles(Storage::PreparedList &&list, SendMediaType type);
void uploadFile(const QByteArray &fileContent, SendMediaType type);
// If an empty filepath is found we upload (possible) "image" with (possible) "content".
void uploadFilesAfterConfirmation(const QStringList &files, const QByteArray &content, const QImage &image, std::unique_ptr<FileLoadTask::MediaInformation> information, SendMediaType type, QString caption);
void uploadFilesAfterConfirmation(
Storage::PreparedList &&list,
const QByteArray &content,
const QImage &image,
SendMediaType type,
QString caption,
MsgId replyTo,
std::shared_ptr<SendingAlbum> album = nullptr);
void itemRemoved(not_null<const HistoryItem*> item);
@ -826,14 +832,13 @@ private:
object_ptr<InlineBots::Layout::Widget> _inlineResults = { nullptr };
object_ptr<TabbedPanel> _tabbedPanel;
QPointer<TabbedSelector> _tabbedSelector;
DragState _attachDrag = DragState::None;
DragState _attachDragState;
object_ptr<DragArea> _attachDragDocument, _attachDragPhoto;
object_ptr<Ui::Emoji::SuggestionsController> _emojiSuggestions = { nullptr };
bool _nonEmptySelection = false;
TaskQueue _fileLoader;
TextUpdateEvents _textUpdateEvents = (TextUpdateEvents() | TextUpdateEvent::SaveDraft | TextUpdateEvent::SendTyping);
int64 _serviceImageCacheSize = 0;

View file

@ -1044,10 +1044,6 @@ void MainWidget::dialogsActivate() {
_dialogs->activate();
}
DragState MainWidget::getDragState(const QMimeData *mime) {
return _history->getDragState(mime);
}
bool MainWidget::leaveChatFailed(PeerData *peer, const RPCError &error) {
if (MTP::isDefaultHandledError(error)) return false;
@ -1384,8 +1380,11 @@ bool MainWidget::sendMessageFail(const RPCError &error) {
Ui::show(Box<InformBox>(PeerFloodErrorText(PeerFloodType::Send)));
return true;
} else if (error.type() == qstr("USER_BANNED_IN_CHANNEL")) {
auto link = textcmdLink(Messenger::Instance().createInternalLinkFull(qsl("spambot")), lang(lng_cant_more_info));
Ui::show(Box<InformBox>(lng_error_public_groups_denied(lt_more_info, link)));
const auto link = textcmdLink(
Messenger::Instance().createInternalLinkFull(qsl("spambot")),
lang(lng_cant_more_info));
const auto text = lng_error_public_groups_denied(lt_more_info, link);
Ui::show(Box<InformBox>(text));
return true;
}
return false;
@ -1977,7 +1976,8 @@ void MainWidget::mediaMarkRead(not_null<HistoryItem*> item) {
}
}
void MainWidget::onSendFileConfirm(const FileLoadResultPtr &file) {
void MainWidget::onSendFileConfirm(
const std::shared_ptr<FileLoadResult> &file) {
_history->sendFileConfirmed(file);
}

View file

@ -20,7 +20,6 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#pragma once
#include "storage/localimageloader.h"
#include "core/single_timer.h"
#include "base/weak_ptr.h"
#include "ui/rp_widget.h"
@ -32,6 +31,7 @@ class DialogsWidget;
class HistoryWidget;
class HistoryHider;
class StackItem;
struct FileLoadResult;
namespace Notify {
struct PeerUpdate;
@ -80,13 +80,6 @@ class ItemBase;
} // namespace Layout
} // namespace InlineBots
enum class DragState {
None = 0x00,
Files = 0x01,
PhotoFiles = 0x02,
Image = 0x03,
};
class MainWidget : public Ui::RpWidget, public RPCSender, private base::Subscriber {
Q_OBJECT
@ -166,7 +159,7 @@ public:
QPixmap grabForShowAnimation(const Window::SectionSlideParams &params);
void checkMainSectionToLayer();
void onSendFileConfirm(const FileLoadResultPtr &file);
void onSendFileConfirm(const std::shared_ptr<FileLoadResult> &file);
bool onSendSticker(DocumentData *sticker);
void destroyData();
@ -208,8 +201,6 @@ public:
void deletePhotoLayer(PhotoData *photo);
DragState getDragState(const QMimeData *mime);
bool leaveChatFailed(PeerData *peer, const RPCError &e);
void deleteHistoryAfterLeave(PeerData *peer, const MTPUpdates &updates);
void deleteMessages(

View file

@ -1499,8 +1499,8 @@ private:
namespace Media {
namespace Player {
FileLoadTask::Song PrepareForSending(const QString &fname, const QByteArray &data) {
auto result = FileLoadTask::Song();
FileMediaInformation::Song PrepareForSending(const QString &fname, const QByteArray &data) {
auto result = FileMediaInformation::Song();
FFMpegAttributesReader reader(FileLocation(fname), data);
const auto positionMs = TimeMs(0);
if (reader.open(positionMs) && reader.samplesCount() > 0) {

View file

@ -310,7 +310,7 @@ private:
};
FileLoadTask::Song PrepareForSending(const QString &fname, const QByteArray &data);
FileMediaInformation::Song PrepareForSending(const QString &fname, const QByteArray &data);
namespace internal {

View file

@ -872,8 +872,8 @@ Manager::~Manager() {
clear();
}
FileLoadTask::Video PrepareForSending(const QString &fname, const QByteArray &data) {
auto result = FileLoadTask::Video();
FileMediaInformation::Video PrepareForSending(const QString &fname, const QByteArray &data) {
auto result = FileMediaInformation::Video();
auto localLocation = FileLocation(fname);
auto localData = QByteArray(data);

View file

@ -249,7 +249,7 @@ private:
};
FileLoadTask::Video PrepareForSending(const QString &fname, const QByteArray &data);
FileMediaInformation::Video PrepareForSending(const QString &fname, const QByteArray &data);
void Finish();

View file

@ -20,6 +20,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#include "storage/file_upload.h"
#include "storage/localimageloader.h"
#include "data/data_document.h"
#include "data/data_photo.h"
@ -30,6 +31,95 @@ constexpr auto kMaxUploadFileParallelSize = MTP::kUploadSessionsCount * 512 * 10
} // namespace
struct Uploader::File {
File(const SendMediaReady &media);
File(const std::shared_ptr<FileLoadResult> &file);
void setDocSize(int32 size);
bool setPartSize(uint32 partSize);
std::shared_ptr<FileLoadResult> file;
SendMediaReady media;
int32 partsCount;
mutable int32 fileSentSize;
uint64 id() const;
SendMediaType type() const;
uint64 thumbId() const;
const QString &filename() const;
HashMd5 md5Hash;
std::unique_ptr<QFile> docFile;
int32 docSentParts = 0;
int32 docSize = 0;
int32 docPartSize = 0;
int32 docPartsCount = 0;
};
Uploader::File::File(const SendMediaReady &media) : media(media) {
partsCount = media.parts.size();
if (type() == SendMediaType::File || type() == SendMediaType::Audio) {
setDocSize(media.file.isEmpty()
? media.data.size()
: media.filesize);
} else {
docSize = docPartSize = docPartsCount = 0;
}
}
Uploader::File::File(const std::shared_ptr<FileLoadResult> &file)
: file(file) {
partsCount = (type() == SendMediaType::Photo)
? file->fileparts.size()
: file->thumbparts.size();
if (type() == SendMediaType::File || type() == SendMediaType::Audio) {
setDocSize(file->filesize);
} else {
docSize = docPartSize = docPartsCount = 0;
}
}
void Uploader::File::setDocSize(int32 size) {
docSize = size;
constexpr auto limit0 = 1024 * 1024;
constexpr auto limit1 = 32 * limit0;
if (docSize >= limit0 || !setPartSize(DocumentUploadPartSize0)) {
if (docSize > limit1 || !setPartSize(DocumentUploadPartSize1)) {
if (!setPartSize(DocumentUploadPartSize2)) {
if (!setPartSize(DocumentUploadPartSize3)) {
if (!setPartSize(DocumentUploadPartSize4)) {
LOG(("Upload Error: bad doc size: %1").arg(docSize));
}
}
}
}
}
}
bool Uploader::File::setPartSize(uint32 partSize) {
docPartSize = partSize;
docPartsCount = (docSize / docPartSize)
+ ((docSize % docPartSize) ? 1 : 0);
return (docPartsCount <= DocumentMaxPartsCount);
}
uint64 Uploader::File::id() const {
return file ? file->id : media.id;
}
SendMediaType Uploader::File::type() const {
return file ? file->type : media.type;
}
uint64 Uploader::File::thumbId() const {
return file ? file->thumbId : media.thumbId;
}
const QString &Uploader::File::filename() const {
return file ? file->filename : media.filename;
}
Uploader::Uploader() {
nextTimer.setSingleShot(true);
connect(&nextTimer, SIGNAL(timeout()), this, SLOT(sendNext()));
@ -59,7 +149,9 @@ void Uploader::uploadMedia(const FullMsgId &msgId, const SendMediaReady &media)
sendNext();
}
void Uploader::upload(const FullMsgId &msgId, const FileLoadResultPtr &file) {
void Uploader::upload(
const FullMsgId &msgId,
const std::shared_ptr<FileLoadResult> &file) {
if (file->type == SendMediaType::Photo) {
auto photo = App::feedPhoto(file->photo, file->photoThumbs);
photo->uploadingData = std::make_unique<PhotoData::UploadingData>(file->partssize);

View file

@ -20,7 +20,8 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#pragma once
#include "storage/localimageloader.h"
struct FileLoadResult;
struct SendMediaReady;
namespace Storage {
@ -30,7 +31,9 @@ class Uploader : public QObject, public RPCSender {
public:
Uploader();
void uploadMedia(const FullMsgId &msgId, const SendMediaReady &image);
void upload(const FullMsgId &msgId, const FileLoadResultPtr &file);
void upload(
const FullMsgId &msgId,
const std::shared_ptr<FileLoadResult> &file);
int32 currentOffset(const FullMsgId &msgId) const; // -1 means file not found
int32 fullSize(const FullMsgId &msgId) const;
@ -60,69 +63,7 @@ signals:
void documentFailed(const FullMsgId &msgId);
private:
struct File {
File(const SendMediaReady &media) : media(media), docSentParts(0) {
partsCount = media.parts.size();
if (type() == SendMediaType::File || type() == SendMediaType::Audio) {
setDocSize(media.file.isEmpty() ? media.data.size() : media.filesize);
} else {
docSize = docPartSize = docPartsCount = 0;
}
}
File(const FileLoadResultPtr &file) : file(file), docSentParts(0) {
partsCount = (type() == SendMediaType::Photo) ? file->fileparts.size() : file->thumbparts.size();
if (type() == SendMediaType::File || type() == SendMediaType::Audio) {
setDocSize(file->filesize);
} else {
docSize = docPartSize = docPartsCount = 0;
}
}
void setDocSize(int32 size) {
docSize = size;
if (docSize >= 1024 * 1024 || !setPartSize(DocumentUploadPartSize0)) {
if (docSize > 32 * 1024 * 1024 || !setPartSize(DocumentUploadPartSize1)) {
if (!setPartSize(DocumentUploadPartSize2)) {
if (!setPartSize(DocumentUploadPartSize3)) {
if (!setPartSize(DocumentUploadPartSize4)) {
LOG(("Upload Error: bad doc size: %1").arg(docSize));
}
}
}
}
}
}
bool setPartSize(uint32 partSize) {
docPartSize = partSize;
docPartsCount = (docSize / docPartSize) + ((docSize % docPartSize) ? 1 : 0);
return (docPartsCount <= DocumentMaxPartsCount);
}
FileLoadResultPtr file;
SendMediaReady media;
int32 partsCount;
mutable int32 fileSentSize;
uint64 id() const {
return file ? file->id : media.id;
}
SendMediaType type() const {
return file ? file->type : media.type;
}
uint64 thumbId() const {
return file ? file->thumbId : media.thumbId;
}
const QString &filename() const {
return file ? file->filename : media.filename;
}
HashMd5 md5Hash;
std::unique_ptr<QFile> docFile;
int32 docSentParts;
int32 docSize;
int32 docPartSize;
int32 docPartsCount;
};
struct File;
void partLoaded(const MTPBool &result, mtpRequestId requestId);
bool partFailed(const RPCError &err, mtpRequestId requestId);

View file

@ -30,21 +30,16 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "lang/lang_keys.h"
#include "boxes/confirm_box.h"
#include "storage/file_download.h"
#include "storage/storage_media_prepare.h"
namespace {
using Storage::ValidateThumbDimensions;
bool ValidateThumbDimensions(int width, int height) {
return (width > 0) && (height > 0) && (width < 20 * height) && (height < 20 * width);
}
} // namespace
TaskQueue::TaskQueue(QObject *parent, int32 stopTimeoutMs) : QObject(parent), _thread(0), _worker(0), _stopTimer(0) {
TaskQueue::TaskQueue(TimeMs stopTimeoutMs) {
if (stopTimeoutMs > 0) {
_stopTimer = new QTimer(this);
connect(_stopTimer, SIGNAL(timeout()), this, SLOT(stop()));
_stopTimer->setSingleShot(true);
_stopTimer->setInterval(stopTimeoutMs);
_stopTimer->setInterval(int(stopTimeoutMs));
}
}
@ -189,23 +184,61 @@ void TaskQueueWorker::onTaskAdded() {
_inTaskAdded = false;
}
FileLoadTask::FileLoadTask(const QString &filepath, std::unique_ptr<MediaInformation> information, SendMediaType type, const FileLoadTo &to, const QString &caption) : _id(rand_value<uint64>())
SendingAlbum::SendingAlbum() : groupId(rand_value<uint64>()) {
}
FileLoadResult::FileLoadResult(
TaskId taskId,
uint64 id,
const FileLoadTo &to,
const QString &caption,
std::shared_ptr<SendingAlbum> album)
: taskId(taskId)
, id(id)
, to(to)
, caption(caption)
, album(std::move(album)) {
}
FileLoadTask::FileLoadTask(
const QString &filepath,
std::unique_ptr<FileMediaInformation> information,
SendMediaType type,
const FileLoadTo &to,
const QString &caption,
std::shared_ptr<SendingAlbum> album)
: _id(rand_value<uint64>())
, _to(to)
, _album(std::move(album))
, _filepath(filepath)
, _information(std::move(information))
, _type(type)
, _caption(caption) {
}
FileLoadTask::FileLoadTask(const QByteArray &content, const QImage &image, SendMediaType type, const FileLoadTo &to, const QString &caption) : _id(rand_value<uint64>())
FileLoadTask::FileLoadTask(
const QByteArray &content,
const QImage &image,
SendMediaType type,
const FileLoadTo &to,
const QString &caption,
std::shared_ptr<SendingAlbum> album)
: _id(rand_value<uint64>())
, _to(to)
, _album(std::move(album))
, _content(content)
, _image(image)
, _type(type)
, _caption(caption) {
}
FileLoadTask::FileLoadTask(const QByteArray &voice, int32 duration, const VoiceWaveform &waveform, const FileLoadTo &to, const QString &caption) : _id(rand_value<uint64>())
FileLoadTask::FileLoadTask(
const QByteArray &voice,
int32 duration,
const VoiceWaveform &waveform,
const FileLoadTo &to,
const QString &caption)
: _id(rand_value<uint64>())
, _to(to)
, _content(voice)
, _duration(duration)
@ -214,8 +247,11 @@ FileLoadTask::FileLoadTask(const QByteArray &voice, int32 duration, const VoiceW
, _caption(caption) {
}
std::unique_ptr<FileLoadTask::MediaInformation> FileLoadTask::ReadMediaInformation(const QString &filepath, const QByteArray &content, const QString &filemime) {
auto result = std::make_unique<MediaInformation>();
std::unique_ptr<FileMediaInformation> FileLoadTask::ReadMediaInformation(
const QString &filepath,
const QByteArray &content,
const QString &filemime) {
auto result = std::make_unique<FileMediaInformation>();
result->filemime = filemime;
if (CheckForSong(filepath, content, result)) {
@ -229,7 +265,11 @@ std::unique_ptr<FileLoadTask::MediaInformation> FileLoadTask::ReadMediaInformati
}
template <typename Mimes, typename Extensions>
bool FileLoadTask::CheckMimeOrExtensions(const QString &filepath, const QString &filemime, Mimes &mimes, Extensions &extensions) {
bool FileLoadTask::CheckMimeOrExtensions(
const QString &filepath,
const QString &filemime,
Mimes &mimes,
Extensions &extensions) {
if (std::find(std::begin(mimes), std::end(mimes), filemime) != std::end(mimes)) {
return true;
}
@ -241,7 +281,10 @@ bool FileLoadTask::CheckMimeOrExtensions(const QString &filepath, const QString
return false;
}
bool FileLoadTask::CheckForSong(const QString &filepath, const QByteArray &content, std::unique_ptr<MediaInformation> &result) {
bool FileLoadTask::CheckForSong(
const QString &filepath,
const QByteArray &content,
std::unique_ptr<FileMediaInformation> &result) {
static const auto mimes = {
qstr("audio/mp3"),
qstr("audio/m4a"),
@ -271,7 +314,10 @@ bool FileLoadTask::CheckForSong(const QString &filepath, const QByteArray &conte
return true;
}
bool FileLoadTask::CheckForVideo(const QString &filepath, const QByteArray &content, std::unique_ptr<MediaInformation> &result) {
bool FileLoadTask::CheckForVideo(
const QString &filepath,
const QByteArray &content,
std::unique_ptr<FileMediaInformation> &result) {
static const auto mimes = {
qstr("video/mp4"),
qstr("video/quicktime"),
@ -302,7 +348,10 @@ bool FileLoadTask::CheckForVideo(const QString &filepath, const QByteArray &cont
return true;
}
bool FileLoadTask::CheckForImage(const QString &filepath, const QByteArray &content, std::unique_ptr<MediaInformation> &result) {
bool FileLoadTask::CheckForImage(
const QString &filepath,
const QByteArray &content,
std::unique_ptr<FileMediaInformation> &result) {
auto animated = false;
auto image = ([&filepath, &content, &animated] {
if (!content.isEmpty()) {
@ -316,7 +365,7 @@ bool FileLoadTask::CheckForImage(const QString &filepath, const QByteArray &cont
if (image.isNull()) {
return false;
}
auto media = Image();
auto media = FileMediaInformation::Image();
media.data = std::move(image);
media.animated = animated;
result->media = media;
@ -326,7 +375,12 @@ bool FileLoadTask::CheckForImage(const QString &filepath, const QByteArray &cont
void FileLoadTask::process() {
const auto stickerMime = qsl("image/webp");
_result = std::make_shared<FileLoadResult>(_id, _to, _caption);
_result = std::make_shared<FileLoadResult>(
id(),
_id,
_to,
_caption,
_album);
QString filename, filemime;
qint64 filesize = 0;
@ -360,7 +414,8 @@ void FileLoadTask::process() {
_information = readMediaInformation(mimeTypeForFile(info).name());
}
filemime = _information->filemime;
if (auto image = base::get_if<FileLoadTask::Image>(&_information->media)) {
if (auto image = base::get_if<FileMediaInformation::Image>(
&_information->media)) {
fullimage = base::take(image->data);
if (auto opaque = (filemime != stickerMime)) {
fullimage = Images::prepareOpaque(std::move(fullimage));
@ -393,7 +448,8 @@ void FileLoadTask::process() {
}
} else {
if (_information) {
if (auto image = base::get_if<FileLoadTask::Image>(&_information->media)) {
if (auto image = base::get_if<FileMediaInformation::Image>(
&_information->media)) {
fullimage = base::take(image->data);
}
}
@ -440,7 +496,8 @@ void FileLoadTask::process() {
_information = readMediaInformation(filemime);
filemime = _information->filemime;
}
if (auto song = base::get_if<Song>(&_information->media)) {
if (auto song = base::get_if<FileMediaInformation::Song>(
&_information->media)) {
isSong = true;
auto flags = MTPDdocumentAttributeAudio::Flag::f_title | MTPDdocumentAttributeAudio::Flag::f_performer;
attributes.push_back(MTP_documentAttributeAudio(MTP_flags(flags), MTP_int(song->duration), MTP_string(song->title), MTP_string(song->performer), MTPstring()));
@ -461,7 +518,8 @@ void FileLoadTask::process() {
thumbId = rand_value<uint64>();
}
} else if (auto video = base::get_if<Video>(&_information->media)) {
} else if (auto video = base::get_if<FileMediaInformation::Video>(
&_information->media)) {
isVideo = true;
auto coverWidth = video->thumbnail.width();
auto coverHeight = video->thumbnail.height();
@ -586,12 +644,27 @@ void FileLoadTask::finish() {
Ui::show(
Box<InformBox>(lng_send_image_empty(lt_name, _filepath)),
LayerOption::KeepOther);
removeFromAlbum();
} else if (_result->filesize > App::kFileSizeLimit) {
Ui::show(
Box<InformBox>(
lng_send_image_too_large(lt_name, _filepath)),
LayerOption::KeepOther);
removeFromAlbum();
} else if (App::main()) {
App::main()->onSendFileConfirm(_result);
}
}
void FileLoadTask::removeFromAlbum() {
if (!_album) {
return;
}
const auto proj = [](const SendingAlbum::Item &item) {
return item.taskId;
};
const auto it = ranges::find(_album->items, id(), proj);
Assert(it != _album->items.end());
_album->items.erase(it);
}

View file

@ -120,7 +120,7 @@ class TaskQueue : public QObject {
Q_OBJECT
public:
TaskQueue(QObject *parent, int32 stopTimeoutMs = 0); // <= 0 - never stop worker
explicit TaskQueue(TimeMs stopTimeoutMs = 0); // <= 0 - never stop worker
TaskId addTask(std::unique_ptr<Task> &&task);
void addTasks(std::vector<std::unique_ptr<Task>> &&tasks);
@ -144,9 +144,9 @@ private:
std::deque<std::unique_ptr<Task>> _tasksToFinish;
TaskId _taskInProcessId = TaskId();
QMutex _tasksToProcessMutex, _tasksToFinishMutex;
QThread *_thread;
TaskQueueWorker *_worker;
QTimer *_stopTimer;
QThread *_thread = nullptr;
TaskQueueWorker *_worker = nullptr;
QTimer *_stopTimer = nullptr;
};
@ -169,6 +169,22 @@ private:
};
struct SendingAlbum {
struct Item {
explicit Item(TaskId taskId) : taskId(taskId) {
}
TaskId taskId;
FullMsgId msgId;
base::optional<MTPInputSingleMedia> media;
};
SendingAlbum();
uint64 groupId;
std::vector<Item> items;
};
struct FileLoadTo {
FileLoadTo(const PeerId &peer, bool silent, MsgId replyTo)
: peer(peer)
@ -181,14 +197,17 @@ struct FileLoadTo {
};
struct FileLoadResult {
FileLoadResult(const uint64 &id, const FileLoadTo &to, const QString &caption)
: id(id)
, to(to)
, caption(caption) {
}
FileLoadResult(
TaskId taskId,
uint64 id,
const FileLoadTo &to,
const QString &caption,
std::shared_ptr<SendingAlbum> album);
TaskId taskId;
uint64 id;
FileLoadTo to;
std::shared_ptr<SendingAlbum> album;
SendMediaType type = SendMediaType::File;
QString filepath;
QByteArray content;
@ -235,10 +254,8 @@ struct FileLoadResult {
}
}
};
using FileLoadResultPtr = std::shared_ptr<FileLoadResult>;
class FileLoadTask final : public Task {
public:
struct FileMediaInformation {
struct Image {
QImage data;
bool animated = false;
@ -254,15 +271,38 @@ public:
int duration = -1;
QImage thumbnail;
};
struct MediaInformation {
QString filemime;
base::variant<Image, Song, Video> media;
};
static std::unique_ptr<MediaInformation> ReadMediaInformation(const QString &filepath, const QByteArray &content, const QString &filemime);
FileLoadTask(const QString &filepath, std::unique_ptr<MediaInformation> information, SendMediaType type, const FileLoadTo &to, const QString &caption);
FileLoadTask(const QByteArray &content, const QImage &image, SendMediaType type, const FileLoadTo &to, const QString &caption);
FileLoadTask(const QByteArray &voice, int32 duration, const VoiceWaveform &waveform, const FileLoadTo &to, const QString &caption);
QString filemime;
base::variant<Image, Song, Video> media;
};
class FileLoadTask final : public Task {
public:
static std::unique_ptr<FileMediaInformation> ReadMediaInformation(
const QString &filepath,
const QByteArray &content,
const QString &filemime);
FileLoadTask(
const QString &filepath,
std::unique_ptr<FileMediaInformation> information,
SendMediaType type,
const FileLoadTo &to,
const QString &caption,
std::shared_ptr<SendingAlbum> album = nullptr);
FileLoadTask(
const QByteArray &content,
const QImage &image,
SendMediaType type,
const FileLoadTo &to,
const QString &caption,
std::shared_ptr<SendingAlbum> album = nullptr);
FileLoadTask(
const QByteArray &voice,
int32 duration,
const VoiceWaveform &waveform,
const FileLoadTo &to,
const QString &caption);
uint64 fileid() const {
return _id;
@ -272,28 +312,30 @@ public:
void finish();
private:
static bool CheckForSong(const QString &filepath, const QByteArray &content, std::unique_ptr<MediaInformation> &result);
static bool CheckForVideo(const QString &filepath, const QByteArray &content, std::unique_ptr<MediaInformation> &result);
static bool CheckForImage(const QString &filepath, const QByteArray &content, std::unique_ptr<MediaInformation> &result);
static bool CheckForSong(const QString &filepath, const QByteArray &content, std::unique_ptr<FileMediaInformation> &result);
static bool CheckForVideo(const QString &filepath, const QByteArray &content, std::unique_ptr<FileMediaInformation> &result);
static bool CheckForImage(const QString &filepath, const QByteArray &content, std::unique_ptr<FileMediaInformation> &result);
template <typename Mimes, typename Extensions>
static bool CheckMimeOrExtensions(const QString &filepath, const QString &filemime, Mimes &mimes, Extensions &extensions);
std::unique_ptr<MediaInformation> readMediaInformation(const QString &filemime) const {
std::unique_ptr<FileMediaInformation> readMediaInformation(const QString &filemime) const {
return ReadMediaInformation(_filepath, _content, filemime);
}
void removeFromAlbum();
uint64 _id;
FileLoadTo _to;
const std::shared_ptr<SendingAlbum> _album;
QString _filepath;
QByteArray _content;
std::unique_ptr<MediaInformation> _information;
std::unique_ptr<FileMediaInformation> _information;
QImage _image;
int32 _duration = 0;
VoiceWaveform _waveform;
SendMediaType _type;
QString _caption;
FileLoadResultPtr _result;
std::shared_ptr<FileLoadResult> _result;
};

View file

@ -44,12 +44,13 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
namespace Local {
namespace {
constexpr int kThemeFileSizeLimit = 5 * 1024 * 1024;
constexpr auto kThemeFileSizeLimit = 5 * 1024 * 1024;
constexpr auto kFileLoaderQueueStopTimeout = TimeMs(5000);
using FileKey = quint64;
constexpr char tdfMagic[] = { 'T', 'D', 'F', '$' };
constexpr int tdfMagicLen = sizeof(tdfMagic);
constexpr auto tdfMagicLen = int(sizeof(tdfMagic));
QString toFilePart(FileKey val) {
QString result;
@ -2273,7 +2274,7 @@ void start() {
Expects(!_manager);
_manager = new internal::Manager();
_localLoader = new TaskQueue(0, FileLoaderQueueStopTimeout);
_localLoader = new TaskQueue(kFileLoaderQueueStopTimeout);
_basePath = cWorkingDir() + qsl("tdata/");
if (!QDir().exists(_basePath)) QDir().mkpath(_basePath);

View file

@ -0,0 +1,236 @@
/*
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 "storage/storage_media_prepare.h"
#include "platform/platform_file_utilities.h"
#include "base/task_queue.h"
#include "storage/localimageloader.h"
namespace Storage {
namespace {
constexpr auto kMaxAlbumCount = 10;
bool HasExtensionFrom(const QString &file, const QStringList &extensions) {
for (const auto &extension : extensions) {
const auto ext = file.right(extension.size());
if (ext.compare(extension, Qt::CaseInsensitive) == 0) {
return true;
}
}
return false;
}
bool ValidPhotoForAlbum(const FileMediaInformation::Image &image) {
if (image.animated) {
return false;
}
const auto width = image.data.width();
const auto height = image.data.height();
return ValidateThumbDimensions(width, height);
}
bool ValidVideoForAlbum(const FileMediaInformation::Video &video) {
const auto width = video.thumbnail.width();
const auto height = video.thumbnail.height();
return ValidateThumbDimensions(width, height);
}
bool PrepareAlbumMediaIsWaiting(
QSemaphore &semaphore,
PreparedFile &file,
int previewWidth) {
// Use some special thread queue, like a separate QThreadPool.
base::TaskQueue::Normal().Put([&, previewWidth] {
const auto guard = gsl::finally([&] { semaphore.release(); });
const auto filemime = mimeTypeForFile(QFileInfo(file.path)).name();
file.information = FileLoadTask::ReadMediaInformation(
file.path,
QByteArray(),
filemime);
using Image = FileMediaInformation::Image;
using Video = FileMediaInformation::Video;
if (const auto image = base::get_if<Image>(
&file.information->media)) {
if (ValidPhotoForAlbum(*image)) {
file.preview = image->data.scaledToWidth(
previewWidth * cIntRetinaFactor(),
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);
file.preview = std::move(blurred).scaledToWidth(
previewWidth * cIntRetinaFactor(),
Qt::SmoothTransformation);
file.preview.setDevicePixelRatio(cRetinaFactor());
file.type = PreparedFile::AlbumType::Video;
}
}
});
return true;
}
void PrepareAlbum(PreparedList &result, int previewWidth) {
const auto count = int(result.files.size());
if ((count < 2) || (count > kMaxAlbumCount)) {
return;
}
result.albumIsPossible = true;
auto waiting = 0;
QSemaphore semaphore;
for (auto &file : result.files) {
if (!result.albumIsPossible) {
break;
}
if (PrepareAlbumMediaIsWaiting(semaphore, file, previewWidth)) {
++waiting;
}
}
if (waiting > 0) {
semaphore.acquire(waiting);
}
}
} // namespace
bool ValidateThumbDimensions(int width, int height) {
return (width > 0)
&& (height > 0)
&& (width < 20 * height)
&& (height < 20 * width);
}
PreparedFile::PreparedFile(const QString &path) : path(path) {
}
PreparedFile::PreparedFile(PreparedFile &&other) = default;
PreparedFile &PreparedFile::operator=(PreparedFile &&other) = default;
PreparedFile::~PreparedFile() = default;
MimeDataState ComputeMimeDataState(const QMimeData *data) {
if (!data
|| data->hasFormat(qsl("application/x-td-forward-selected"))
|| data->hasFormat(qsl("application/x-td-forward-pressed"))
|| data->hasFormat(qsl("application/x-td-forward-pressed-link"))) {
return MimeDataState::None;
}
if (data->hasImage()) {
return MimeDataState::Image;
}
const auto uriListFormat = qsl("text/uri-list");
if (!data->hasFormat(uriListFormat)) {
return MimeDataState::None;
}
const auto &urls = data->urls();
if (urls.isEmpty()) {
return MimeDataState::None;
}
const auto imageExtensions = cImgExtensions();
auto files = QStringList();
auto allAreSmallImages = true;
for (const auto &url : urls) {
if (!url.isLocalFile()) {
return MimeDataState::None;
}
const auto file = Platform::File::UrlToLocal(url);
const auto info = QFileInfo(file);
if (info.isDir()) {
return MimeDataState::None;
}
const auto filesize = info.size();
if (filesize > App::kFileSizeLimit) {
return MimeDataState::None;
} else if (allAreSmallImages) {
if (filesize > App::kImageSizeLimit) {
allAreSmallImages = false;
} else if (!HasExtensionFrom(file, imageExtensions)) {
allAreSmallImages = false;
}
}
}
return allAreSmallImages
? MimeDataState::PhotoFiles
: MimeDataState::Files;
}
PreparedList PrepareMediaList(const QList<QUrl> &files, int previewWidth) {
auto locals = QStringList();
locals.reserve(files.size());
for (const auto &url : files) {
if (!url.isLocalFile()) {
return {
PreparedList::Error::NonLocalUrl,
url.toDisplayString()
};
}
locals.push_back(Platform::File::UrlToLocal(url));
}
return PrepareMediaList(locals, previewWidth);
}
PreparedList PrepareMediaList(const QStringList &files, int previewWidth) {
auto result = PreparedList();
result.files.reserve(files.size());
const auto extensionsToCompress = cExtensionsForCompress();
for (const auto &file : files) {
const auto fileinfo = QFileInfo(file);
const auto filesize = fileinfo.size();
if (fileinfo.isDir()) {
return {
PreparedList::Error::Directory,
file
};
} else if (filesize <= 0) {
return {
PreparedList::Error::EmptyFile,
file
};
} else if (filesize > App::kFileSizeLimit) {
return {
PreparedList::Error::TooLargeFile,
file
};
}
const auto toCompress = HasExtensionFrom(file, extensionsToCompress);
if (filesize > App::kImageSizeLimit || !toCompress) {
result.allFilesForCompress = false;
}
result.files.push_back({ file });
}
PrepareAlbum(result, previewWidth);
return result;
}
} // namespace Storage

View file

@ -0,0 +1,83 @@
/*
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
struct FileMediaInformation;
namespace Storage {
enum class MimeDataState {
None,
Files,
PhotoFiles,
Image,
};
MimeDataState ComputeMimeDataState(const QMimeData *data);
struct PreparedFile {
enum class AlbumType {
None,
Photo,
Video,
};
PreparedFile(const QString &path);
PreparedFile(PreparedFile &&other);
PreparedFile &operator=(PreparedFile &&other);
~PreparedFile();
QString path;
std::unique_ptr<FileMediaInformation> information;
base::optional<QImage> large;
QImage preview;
AlbumType type = AlbumType::None;
};
struct PreparedList {
enum class Error {
None,
NonLocalUrl,
Directory,
EmptyFile,
TooLargeFile,
};
PreparedList() = default;
PreparedList(Error error, QString errorData)
: error(error)
, errorData(errorData) {
}
Error error = Error::None;
QString errorData;
std::vector<PreparedFile> files;
bool allFilesForCompress = true;
bool albumIsPossible = false;
};
bool ValidateThumbDimensions(int width, int height);
PreparedList PrepareMediaList(const QList<QUrl> &files, int previewWidth);
PreparedList PrepareMediaList(const QStringList &files, int previewWidth);
} // namespace Storage

View file

@ -20,7 +20,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#include "ui/grouped_layout.h"
namespace Data {
namespace Ui {
namespace {
int Round(float64 value) {
@ -586,4 +586,47 @@ std::vector<GroupMediaLayout> LayoutMediaGroup(
return Layouter(sizes, maxWidth, minWidth, spacing).layout();
}
} // namespace Data
RectParts GetCornersFromSides(RectParts sides) {
const auto convert = [&](
RectPart side1,
RectPart side2,
RectPart corner) {
return ((sides & side1) && (sides & side2))
? corner
: RectPart::None;
};
return RectPart::None
| convert(RectPart::Top, RectPart::Left, RectPart::TopLeft)
| convert(RectPart::Top, RectPart::Right, RectPart::TopRight)
| convert(RectPart::Bottom, RectPart::Left, RectPart::BottomLeft)
| convert(RectPart::Bottom, RectPart::Right, RectPart::BottomRight);
}
QSize GetImageScaleSizeForGeometry(QSize original, QSize geometry) {
const auto width = geometry.width();
const auto height = geometry.height();
auto tw = original.width();
auto th = original.height();
if (tw * height > th * width) {
if (th > height || tw * height < 2 * th * width) {
tw = (height * tw) / th;
th = height;
} else if (tw < width) {
th = (width * th) / tw;
tw = width;
}
} else {
if (tw > width || th * width < 2 * tw * height) {
th = (width * th) / tw;
tw = width;
} else if (tw > 0 && th < height) {
tw = (height * tw) / th;
th = height;
}
}
if (tw < 1) tw = 1;
if (th < 1) th = 1;
return { tw, th };
}
} // namespace Ui

View file

@ -20,7 +20,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#pragma once
namespace Data {
namespace Ui {
struct GroupMediaLayout {
QRect geometry;
@ -33,4 +33,7 @@ std::vector<GroupMediaLayout> LayoutMediaGroup(
int minWidth,
int spacing);
} // namespace Data
RectParts GetCornersFromSides(RectParts sides);
QSize GetImageScaleSizeForGeometry(QSize original, QSize geometry);
} // namespace Ui

View file

@ -519,6 +519,8 @@
<(src_loc)/storage/serialize_document.h
<(src_loc)/storage/storage_facade.cpp
<(src_loc)/storage/storage_facade.h
<(src_loc)/storage/storage_media_prepare.cpp
<(src_loc)/storage/storage_media_prepare.h
<(src_loc)/storage/storage_shared_media.cpp
<(src_loc)/storage/storage_shared_media.h
<(src_loc)/storage/storage_sparse_ids_list.cpp