| 
									
										
										
										
											2020-10-13 15:07:53 +03:00
										 |  |  | /*
 | 
					
						
							|  |  |  | This file is part of Telegram Desktop, | 
					
						
							|  |  |  | the official desktop application for the Telegram messaging service. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | For license and copyright information please follow this link: | 
					
						
							|  |  |  | https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 | 
					
						
							|  |  |  | */ | 
					
						
							|  |  |  | #include "ui/chat/attach/attach_prepare.h"
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-02-20 06:30:14 +03:00
										 |  |  | #include "ui/rp_widget.h"
 | 
					
						
							|  |  |  | #include "ui/widgets/popup_menu.h"
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-17 12:51:55 +03:00
										 |  |  | #include "ui/chat/attach/attach_send_files_way.h"
 | 
					
						
							| 
									
										
										
										
											2021-01-10 02:04:03 +03:00
										 |  |  | #include "ui/image/image_prepare.h"
 | 
					
						
							|  |  |  | #include "ui/ui_utility.h"
 | 
					
						
							| 
									
										
										
										
											2020-10-13 15:07:53 +03:00
										 |  |  | #include "core/mime_type.h"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | namespace Ui { | 
					
						
							|  |  |  | namespace { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | constexpr auto kMaxAlbumCount = 10; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | } // namespace
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | PreparedFile::PreparedFile(const QString &path) : path(path) { | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | PreparedFile::PreparedFile(PreparedFile &&other) = default; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-17 12:51:55 +03:00
										 |  |  | PreparedFile &PreparedFile::operator=(PreparedFile &&other) = default; | 
					
						
							| 
									
										
										
										
											2020-10-13 15:07:53 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | PreparedFile::~PreparedFile() = default; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-20 11:19:48 +03:00
										 |  |  | bool PreparedFile::canBeInAlbumType(AlbumType album) const { | 
					
						
							|  |  |  | 	return CanBeInAlbumType(type, album); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | AlbumType PreparedFile::albumType(bool sendImagesAsPhotos) const { | 
					
						
							|  |  |  | 	switch (type) { | 
					
						
							|  |  |  | 	case Type::Photo: | 
					
						
							|  |  |  | 		return sendImagesAsPhotos ? AlbumType::PhotoVideo : AlbumType::File; | 
					
						
							|  |  |  | 	case Type::Video: | 
					
						
							|  |  |  | 		return AlbumType::PhotoVideo; | 
					
						
							|  |  |  | 	case Type::Music: | 
					
						
							|  |  |  | 		return AlbumType::Music; | 
					
						
							|  |  |  | 	case Type::File: | 
					
						
							|  |  |  | 		return AlbumType::File; | 
					
						
							|  |  |  | 	case Type::None: | 
					
						
							|  |  |  | 		return AlbumType::None; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	Unexpected("PreparedFile::type in PreparedFile::albumType()."); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | bool CanBeInAlbumType(PreparedFile::Type type, AlbumType album) { | 
					
						
							|  |  |  | 	Expects(album != AlbumType::None); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	using Type = PreparedFile::Type; | 
					
						
							|  |  |  | 	switch (album) { | 
					
						
							|  |  |  | 	case AlbumType::PhotoVideo: | 
					
						
							|  |  |  | 		return (type == Type::Photo) || (type == Type::Video); | 
					
						
							|  |  |  | 	case AlbumType::Music: | 
					
						
							|  |  |  | 		return (type == Type::Music); | 
					
						
							|  |  |  | 	case AlbumType::File: | 
					
						
							|  |  |  | 		return (type == Type::Photo) || (type == Type::File); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	Unexpected("AlbumType in CanBeInAlbumType."); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-13 15:07:53 +03:00
										 |  |  | PreparedList PreparedList::Reordered( | 
					
						
							|  |  |  | 		PreparedList &&list, | 
					
						
							|  |  |  | 		std::vector<int> order) { | 
					
						
							|  |  |  | 	Expects(list.error == PreparedList::Error::None); | 
					
						
							|  |  |  | 	Expects(list.files.size() == order.size()); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	auto result = PreparedList(list.error, list.errorData); | 
					
						
							|  |  |  | 	result.files.reserve(list.files.size()); | 
					
						
							|  |  |  | 	for (auto index : order) { | 
					
						
							|  |  |  | 		result.files.push_back(std::move(list.files[index])); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return result; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void PreparedList::mergeToEnd(PreparedList &&other, bool cutToAlbumSize) { | 
					
						
							|  |  |  | 	if (error != Error::None) { | 
					
						
							|  |  |  | 		return; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if (other.error != Error::None) { | 
					
						
							|  |  |  | 		error = other.error; | 
					
						
							|  |  |  | 		errorData = other.errorData; | 
					
						
							|  |  |  | 		return; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	files.reserve(std::min( | 
					
						
							|  |  |  | 		size_t(cutToAlbumSize ? kMaxAlbumCount : INT_MAX), | 
					
						
							|  |  |  | 		files.size() + other.files.size())); | 
					
						
							|  |  |  | 	for (auto &file : other.files) { | 
					
						
							|  |  |  | 		if (cutToAlbumSize && files.size() == kMaxAlbumCount) { | 
					
						
							|  |  |  | 			break; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		files.push_back(std::move(file)); | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2020-10-16 10:52:55 +03:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | bool PreparedList::canBeSentInSlowmode() const { | 
					
						
							| 
									
										
										
										
											2020-10-16 17:11:23 +03:00
										 |  |  | 	return canBeSentInSlowmodeWith(PreparedList()); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | bool PreparedList::canBeSentInSlowmodeWith(const PreparedList &other) const { | 
					
						
							|  |  |  | 	if (!filesToProcess.empty() || !other.filesToProcess.empty()) { | 
					
						
							| 
									
										
										
										
											2020-10-16 10:52:55 +03:00
										 |  |  | 		return false; | 
					
						
							| 
									
										
										
										
											2020-10-16 17:11:23 +03:00
										 |  |  | 	} else if (files.size() + other.files.size() < 2) { | 
					
						
							| 
									
										
										
										
											2020-10-16 10:52:55 +03:00
										 |  |  | 		return true; | 
					
						
							| 
									
										
										
										
											2020-10-16 17:11:23 +03:00
										 |  |  | 	} else if (files.size() + other.files.size() > kMaxAlbumCount) { | 
					
						
							| 
									
										
										
										
											2020-10-16 10:52:55 +03:00
										 |  |  | 		return false; | 
					
						
							| 
									
										
										
										
											2020-10-13 15:07:53 +03:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2020-10-16 10:52:55 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-20 11:19:48 +03:00
										 |  |  | 	using Type = PreparedFile::Type; | 
					
						
							| 
									
										
										
										
											2021-03-13 16:12:08 +04:00
										 |  |  | 	auto &&all = ranges::views::concat(files, other.files); | 
					
						
							| 
									
										
										
										
											2020-10-19 13:25:28 +03:00
										 |  |  | 	const auto has = [&](Type type) { | 
					
						
							|  |  |  | 		return ranges::contains(all, type, &PreparedFile::type); | 
					
						
							|  |  |  | 	}; | 
					
						
							|  |  |  | 	const auto hasNonGrouping = has(Type::None); | 
					
						
							|  |  |  | 	const auto hasPhotos = has(Type::Photo); | 
					
						
							|  |  |  | 	const auto hasFiles = has(Type::File); | 
					
						
							|  |  |  | 	const auto hasVideos = has(Type::Video); | 
					
						
							|  |  |  | 	const auto hasMusic = has(Type::Music); | 
					
						
							| 
									
										
										
										
											2020-10-16 10:52:55 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// File-s and Video-s never can be grouped.
 | 
					
						
							| 
									
										
										
										
											2020-10-19 13:25:28 +03:00
										 |  |  | 	// Music-s can be grouped only with themselves.
 | 
					
						
							|  |  |  | 	if (hasNonGrouping) { | 
					
						
							|  |  |  | 		return false; | 
					
						
							|  |  |  | 	} else if (hasFiles) { | 
					
						
							|  |  |  | 		return !hasMusic && !hasVideos; | 
					
						
							|  |  |  | 	} else if (hasVideos) { | 
					
						
							|  |  |  | 		return !hasMusic && !hasFiles; | 
					
						
							|  |  |  | 	} else if (hasMusic) { | 
					
						
							|  |  |  | 		return !hasVideos && !hasFiles && !hasPhotos; | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2020-10-16 13:38:12 +03:00
										 |  |  | 	return !hasNonGrouping && (!hasFiles || !hasVideos); | 
					
						
							| 
									
										
										
										
											2020-10-13 15:07:53 +03:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-17 12:51:55 +03:00
										 |  |  | bool PreparedList::canAddCaption(bool sendingAlbum) const { | 
					
						
							| 
									
										
										
										
											2020-10-16 10:52:55 +03:00
										 |  |  | 	if (!filesToProcess.empty() | 
					
						
							|  |  |  | 		|| files.empty() | 
					
						
							|  |  |  | 		|| files.size() > kMaxAlbumCount) { | 
					
						
							|  |  |  | 		return false; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if (files.size() == 1) { | 
					
						
							| 
									
										
										
										
											2020-10-16 17:11:23 +03:00
										 |  |  | 		Assert(files.front().information != nullptr); | 
					
						
							|  |  |  | 		const auto isSticker = Core::IsMimeSticker( | 
					
						
							|  |  |  | 			files.front().information->filemime) | 
					
						
							| 
									
										
										
										
											2020-10-13 15:07:53 +03:00
										 |  |  | 			|| files.front().path.endsWith( | 
					
						
							|  |  |  | 				qstr(".tgs"), | 
					
						
							|  |  |  | 				Qt::CaseInsensitive); | 
					
						
							| 
									
										
										
										
											2020-10-16 10:52:55 +03:00
										 |  |  | 		return !isSticker; | 
					
						
							| 
									
										
										
										
											2020-10-17 12:51:55 +03:00
										 |  |  | 	} else if (!sendingAlbum) { | 
					
						
							| 
									
										
										
										
											2020-10-16 10:52:55 +03:00
										 |  |  | 		return false; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	const auto hasFiles = ranges::contains( | 
					
						
							|  |  |  | 		files, | 
					
						
							| 
									
										
										
										
											2020-10-20 11:19:48 +03:00
										 |  |  | 		PreparedFile::Type::File, | 
					
						
							| 
									
										
										
										
											2020-10-16 10:52:55 +03:00
										 |  |  | 		&PreparedFile::type); | 
					
						
							| 
									
										
										
										
											2020-11-02 10:35:38 +03:00
										 |  |  | 	const auto hasMusic = ranges::contains( | 
					
						
							|  |  |  | 		files, | 
					
						
							|  |  |  | 		PreparedFile::Type::Music, | 
					
						
							|  |  |  | 		&PreparedFile::type); | 
					
						
							| 
									
										
										
										
											2020-10-17 12:51:55 +03:00
										 |  |  | 	const auto hasNotGrouped = ranges::contains( | 
					
						
							|  |  |  | 		files, | 
					
						
							| 
									
										
										
										
											2020-10-20 11:19:48 +03:00
										 |  |  | 		PreparedFile::Type::None, | 
					
						
							| 
									
										
										
										
											2020-10-17 12:51:55 +03:00
										 |  |  | 		&PreparedFile::type); | 
					
						
							| 
									
										
										
										
											2020-11-02 10:35:38 +03:00
										 |  |  | 	return !hasFiles && !hasMusic && !hasNotGrouped; | 
					
						
							| 
									
										
										
										
											2020-10-13 15:07:53 +03:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-19 18:37:59 +03:00
										 |  |  | bool PreparedList::hasGroupOption(bool slowmode) const { | 
					
						
							|  |  |  | 	if (slowmode || files.size() < 2) { | 
					
						
							|  |  |  | 		return false; | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2020-10-20 11:19:48 +03:00
										 |  |  | 	using Type = PreparedFile::Type; | 
					
						
							| 
									
										
										
										
											2020-10-19 18:37:59 +03:00
										 |  |  | 	auto lastType = Type::None; | 
					
						
							|  |  |  | 	for (const auto &file : files) { | 
					
						
							|  |  |  | 		if ((file.type == lastType) | 
					
						
							|  |  |  | 			|| (file.type == Type::Video && lastType == Type::Photo) | 
					
						
							|  |  |  | 			|| (file.type == Type::Photo && lastType == Type::Video) | 
					
						
							|  |  |  | 			|| (file.type == Type::File && lastType == Type::Photo) | 
					
						
							|  |  |  | 			|| (file.type == Type::Photo && lastType == Type::File)) { | 
					
						
							|  |  |  | 			if (lastType != Type::None) { | 
					
						
							|  |  |  | 				return true; | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		lastType = file.type; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return false; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | bool PreparedList::hasSendImagesAsPhotosOption(bool slowmode) const { | 
					
						
							| 
									
										
										
										
											2020-10-20 11:19:48 +03:00
										 |  |  | 	using Type = PreparedFile::Type; | 
					
						
							| 
									
										
										
										
											2020-10-19 18:37:59 +03:00
										 |  |  | 	return slowmode | 
					
						
							|  |  |  | 		? ((files.size() == 1) && (files.front().type == Type::Photo)) | 
					
						
							|  |  |  | 		: ranges::contains(files, Type::Photo, &PreparedFile::type); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-13 15:07:53 +03:00
										 |  |  | int MaxAlbumItems() { | 
					
						
							|  |  |  | 	return kMaxAlbumCount; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-13 20:15:52 +03:00
										 |  |  | bool ValidateThumbDimensions(int width, int height) { | 
					
						
							|  |  |  | 	return (width > 0) | 
					
						
							|  |  |  | 		&& (height > 0) | 
					
						
							| 
									
										
										
										
											2021-11-02 09:59:06 +04:00
										 |  |  | 		&& (width <= 20 * height) | 
					
						
							|  |  |  | 		&& (height <= 20 * width); | 
					
						
							| 
									
										
										
										
											2020-10-13 20:15:52 +03:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-17 12:51:55 +03:00
										 |  |  | std::vector<PreparedGroup> DivideByGroups( | 
					
						
							|  |  |  | 		PreparedList &&list, | 
					
						
							|  |  |  | 		SendFilesWay way, | 
					
						
							|  |  |  | 		bool slowmode) { | 
					
						
							|  |  |  | 	const auto sendImagesAsPhotos = way.sendImagesAsPhotos(); | 
					
						
							|  |  |  | 	const auto groupFiles = way.groupFiles() || slowmode; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	auto group = Ui::PreparedList(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-20 11:19:48 +03:00
										 |  |  | 	using Type = Ui::PreparedFile::Type; | 
					
						
							|  |  |  | 	auto groupType = AlbumType::None; | 
					
						
							| 
									
										
										
										
											2020-10-17 12:51:55 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	auto result = std::vector<PreparedGroup>(); | 
					
						
							|  |  |  | 	auto pushGroup = [&] { | 
					
						
							| 
									
										
										
										
											2020-10-22 12:13:00 +03:00
										 |  |  | 		const auto type = (group.files.size() > 1) | 
					
						
							|  |  |  | 			? groupType | 
					
						
							|  |  |  | 			: AlbumType::None; | 
					
						
							| 
									
										
										
										
											2020-10-17 12:51:55 +03:00
										 |  |  | 		result.push_back(PreparedGroup{ | 
					
						
							|  |  |  | 			.list = base::take(group), | 
					
						
							| 
									
										
										
										
											2020-10-22 12:13:00 +03:00
										 |  |  | 			.type = type, | 
					
						
							| 
									
										
										
										
											2020-10-17 12:51:55 +03:00
										 |  |  | 		}); | 
					
						
							|  |  |  | 	}; | 
					
						
							|  |  |  | 	for (auto i = 0; i != list.files.size(); ++i) { | 
					
						
							|  |  |  | 		auto &file = list.files[i]; | 
					
						
							| 
									
										
										
										
											2020-10-19 13:25:28 +03:00
										 |  |  | 		const auto fileGroupType = (file.type == Type::Music) | 
					
						
							| 
									
										
										
										
											2020-10-20 11:19:48 +03:00
										 |  |  | 			? (groupFiles ? AlbumType::Music : AlbumType::None) | 
					
						
							| 
									
										
										
										
											2020-10-19 13:25:28 +03:00
										 |  |  | 			: (file.type == Type::Video) | 
					
						
							| 
									
										
										
										
											2020-10-20 11:19:48 +03:00
										 |  |  | 			? (groupFiles ? AlbumType::PhotoVideo : AlbumType::None) | 
					
						
							| 
									
										
										
										
											2020-10-17 12:51:55 +03:00
										 |  |  | 			: (file.type == Type::Photo) | 
					
						
							|  |  |  | 			? ((groupFiles && sendImagesAsPhotos) | 
					
						
							| 
									
										
										
										
											2020-10-20 11:19:48 +03:00
										 |  |  | 				? AlbumType::PhotoVideo | 
					
						
							| 
									
										
										
										
											2020-10-17 12:51:55 +03:00
										 |  |  | 				: (groupFiles && !sendImagesAsPhotos) | 
					
						
							| 
									
										
										
										
											2020-10-20 11:19:48 +03:00
										 |  |  | 				? AlbumType::File | 
					
						
							|  |  |  | 				: AlbumType::None) | 
					
						
							| 
									
										
										
										
											2020-10-17 12:51:55 +03:00
										 |  |  | 			: (file.type == Type::File) | 
					
						
							| 
									
										
										
										
											2020-10-20 11:19:48 +03:00
										 |  |  | 			? (groupFiles ? AlbumType::File : AlbumType::None) | 
					
						
							|  |  |  | 			: AlbumType::None; | 
					
						
							| 
									
										
										
										
											2020-10-17 12:51:55 +03:00
										 |  |  | 		if ((!group.files.empty() && groupType != fileGroupType) | 
					
						
							| 
									
										
										
										
											2020-10-20 11:19:48 +03:00
										 |  |  | 			|| ((groupType != AlbumType::None) | 
					
						
							| 
									
										
										
										
											2020-10-17 12:51:55 +03:00
										 |  |  | 				&& (group.files.size() == Ui::MaxAlbumItems()))) { | 
					
						
							|  |  |  | 			pushGroup(); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		group.files.push_back(std::move(file)); | 
					
						
							|  |  |  | 		groupType = fileGroupType; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if (!group.files.empty()) { | 
					
						
							|  |  |  | 		pushGroup(); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return result; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-10 02:04:03 +03:00
										 |  |  | QPixmap PrepareSongCoverForThumbnail(QImage image, int size) { | 
					
						
							|  |  |  | 	const auto scaledSize = image.size().scaled( | 
					
						
							|  |  |  | 		size, | 
					
						
							|  |  |  | 		size, | 
					
						
							|  |  |  | 		Qt::KeepAspectRatioByExpanding); | 
					
						
							|  |  |  | 	using Option = Images::Option; | 
					
						
							| 
									
										
										
										
											2022-01-21 15:31:39 +03:00
										 |  |  | 	const auto ratio = style::DevicePixelRatio(); | 
					
						
							|  |  |  | 	return PixmapFromImage(Images::Prepare( | 
					
						
							| 
									
										
										
										
											2021-01-10 02:04:03 +03:00
										 |  |  | 		std::move(image), | 
					
						
							| 
									
										
										
										
											2022-01-21 15:31:39 +03:00
										 |  |  | 		scaledSize * ratio, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			.colored = &st::songCoverOverlayFg, | 
					
						
							|  |  |  | 			.options = Option::RoundCircle, | 
					
						
							|  |  |  | 			.outer = { size, size }, | 
					
						
							|  |  |  | 		})); | 
					
						
							| 
									
										
										
										
											2021-01-10 02:04:03 +03:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-13 15:07:53 +03:00
										 |  |  | } // namespace Ui
 |