"Fossies" - the Fresh Open Source Software Archive

Member "tdesktop-4.8.1/Telegram/SourceFiles/boxes/send_files_box.cpp" (24 Apr 2023, 38969 Bytes) of package /linux/misc/tdesktop-4.8.1.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "send_files_box.cpp" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 4.8.0_vs_4.8.1.

    1 /*
    2 This file is part of Telegram Desktop,
    3 the official desktop application for the Telegram messaging service.
    4 
    5 For license and copyright information please follow this link:
    6 https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
    7 */
    8 #include "boxes/send_files_box.h"
    9 
   10 #include "lang/lang_keys.h"
   11 #include "storage/localstorage.h"
   12 #include "storage/storage_media_prepare.h"
   13 #include "mainwidget.h"
   14 #include "main/main_session.h"
   15 #include "main/main_session_settings.h"
   16 #include "mtproto/mtproto_config.h"
   17 #include "chat_helpers/message_field.h"
   18 #include "menu/menu_send.h"
   19 #include "chat_helpers/emoji_suggestions_widget.h"
   20 #include "chat_helpers/tabbed_panel.h"
   21 #include "chat_helpers/tabbed_selector.h"
   22 #include "editor/photo_editor_layer_widget.h"
   23 #include "history/history_drag_area.h"
   24 #include "history/view/history_view_schedule_box.h"
   25 #include "core/file_utilities.h"
   26 #include "core/mime_type.h"
   27 #include "base/event_filter.h"
   28 #include "base/call_delayed.h"
   29 #include "boxes/premium_limits_box.h"
   30 #include "boxes/premium_preview_box.h"
   31 #include "ui/boxes/confirm_box.h"
   32 #include "ui/effects/animations.h"
   33 #include "ui/effects/scroll_content_shadow.h"
   34 #include "ui/widgets/checkbox.h"
   35 #include "ui/widgets/buttons.h"
   36 #include "ui/widgets/input_fields.h"
   37 #include "ui/widgets/scroll_area.h"
   38 #include "ui/widgets/popup_menu.h"
   39 #include "ui/wrap/vertical_layout.h"
   40 #include "ui/chat/attach/attach_prepare.h"
   41 #include "ui/chat/attach/attach_send_files_way.h"
   42 #include "ui/chat/attach/attach_album_preview.h"
   43 #include "ui/chat/attach/attach_single_file_preview.h"
   44 #include "ui/chat/attach/attach_single_media_preview.h"
   45 #include "ui/text/format_values.h"
   46 #include "ui/grouped_layout.h"
   47 #include "ui/text/text_options.h"
   48 #include "ui/toast/toast.h"
   49 #include "ui/controls/emoji_button.h"
   50 #include "ui/painter.h"
   51 #include "lottie/lottie_single_player.h"
   52 #include "data/data_document.h"
   53 #include "data/data_user.h"
   54 #include "data/data_premium_limits.h"
   55 #include "data/stickers/data_stickers.h"
   56 #include "data/stickers/data_custom_emoji.h"
   57 #include "media/clip/media_clip_reader.h"
   58 #include "api/api_common.h"
   59 #include "window/window_session_controller.h"
   60 #include "core/application.h"
   61 #include "core/core_settings.h"
   62 #include "styles/style_chat.h"
   63 #include "styles/style_layers.h"
   64 #include "styles/style_boxes.h"
   65 #include "styles/style_chat_helpers.h"
   66 #include "styles/style_info.h"
   67 #include "styles/style_menu_icons.h"
   68 
   69 #include <QtCore/QMimeData>
   70 
   71 namespace {
   72 
   73 constexpr auto kMaxMessageLength = 4096;
   74 
   75 using Ui::SendFilesWay;
   76 
   77 inline bool CanAddUrls(const QList<QUrl> &urls) {
   78     return !urls.isEmpty() && ranges::all_of(urls, &QUrl::isLocalFile);
   79 }
   80 
   81 void FileDialogCallback(
   82         FileDialog::OpenResult &&result,
   83         Fn<bool(const Ui::PreparedList&)> checkResult,
   84         Fn<void(Ui::PreparedList)> callback,
   85         bool premium,
   86         not_null<QWidget*> toastParent) {
   87     auto showError = [=](tr::phrase<> text) {
   88         Ui::Toast::Show(toastParent, text(tr::now));
   89     };
   90 
   91     auto list = Storage::PreparedFileFromFilesDialog(
   92         std::move(result),
   93         checkResult,
   94         showError,
   95         st::sendMediaPreviewSize,
   96         premium);
   97 
   98     if (!list) {
   99         return;
  100     }
  101 
  102     callback(std::move(*list));
  103 }
  104 
  105 rpl::producer<QString> FieldPlaceholder(
  106         const Ui::PreparedList &list,
  107         SendFilesWay way) {
  108     return list.canAddCaption(
  109             way.groupFiles() && way.sendImagesAsPhotos(),
  110             way.sendImagesAsPhotos())
  111         ? tr::lng_photo_caption()
  112         : tr::lng_photos_comment();
  113 }
  114 
  115 } // namespace
  116 
  117 SendFilesLimits DefaultLimitsForPeer(not_null<PeerData*> peer) {
  118     using Flag = SendFilesAllow;
  119     using Restriction = ChatRestriction;
  120     const auto allowByRestriction = [&](Restriction check, Flag allow) {
  121         return Data::RestrictionError(peer, check) ? Flag() : allow;
  122     };
  123     return Flag()
  124         | (peer->slowmodeApplied() ? Flag::OnlyOne : Flag())
  125         | (Data::AllowEmojiWithoutPremium(peer)
  126             ? Flag::EmojiWithoutPremium
  127             : Flag())
  128         | allowByRestriction(Restriction::SendPhotos, Flag::Photos)
  129         | allowByRestriction(Restriction::SendVideos, Flag::Videos)
  130         | allowByRestriction(Restriction::SendMusic, Flag::Music)
  131         | allowByRestriction(Restriction::SendFiles, Flag::Files)
  132         | allowByRestriction(Restriction::SendStickers, Flag::Stickers)
  133         | allowByRestriction(Restriction::SendGifs, Flag::Gifs)
  134         | allowByRestriction(Restriction::SendOther, Flag::Texts);
  135 }
  136 
  137 SendFilesCheck DefaultCheckForPeer(
  138         not_null<Window::SessionController*> controller,
  139         not_null<PeerData*> peer) {
  140     return [=](
  141             const Ui::PreparedFile &file,
  142             bool compress,
  143             bool silent) {
  144         const auto error = Data::FileRestrictionError(peer, file, compress);
  145         if (error && !silent) {
  146             controller->showToast({ *error });
  147         }
  148         return !error.has_value();
  149     };
  150 }
  151 
  152 SendFilesBox::Block::Block(
  153     not_null<QWidget*> parent,
  154     not_null<std::vector<Ui::PreparedFile>*> items,
  155     int from,
  156     int till,
  157     Fn<bool()> gifPaused,
  158     SendFilesWay way)
  159 : _items(items)
  160 , _from(from)
  161 , _till(till) {
  162     Expects(from >= 0);
  163     Expects(till > from);
  164     Expects(till <= items->size());
  165 
  166     const auto count = till - from;
  167     const auto my = gsl::make_span(*items).subspan(from, count);
  168     const auto &first = my.front();
  169     _isAlbum = (my.size() > 1);
  170     if (_isAlbum) {
  171         const auto preview = Ui::CreateChild<Ui::AlbumPreview>(
  172             parent.get(),
  173             my,
  174             way);
  175         _preview.reset(preview);
  176     } else {
  177         const auto media = Ui::SingleMediaPreview::Create(
  178             parent,
  179             gifPaused,
  180             first);
  181         if (media) {
  182             _isSingleMedia = true;
  183             _preview.reset(media);
  184         } else {
  185             _preview.reset(
  186                 Ui::CreateChild<Ui::SingleFilePreview>(parent.get(), first));
  187         }
  188     }
  189     _preview->show();
  190 }
  191 
  192 int SendFilesBox::Block::fromIndex() const {
  193     return _from;
  194 }
  195 
  196 int SendFilesBox::Block::tillIndex() const {
  197     return _till;
  198 }
  199 
  200 object_ptr<Ui::RpWidget> SendFilesBox::Block::takeWidget() {
  201     return object_ptr<Ui::RpWidget>::fromRaw(_preview.get());
  202 }
  203 
  204 rpl::producer<int> SendFilesBox::Block::itemDeleteRequest() const {
  205     using namespace rpl::mappers;
  206 
  207     const auto preview = _preview.get();
  208     const auto from = _from;
  209     if (_isAlbum) {
  210         const auto album = static_cast<Ui::AlbumPreview*>(_preview.get());
  211         return album->thumbDeleted() | rpl::map(_1 + from);
  212     } else if (_isSingleMedia) {
  213         const auto media = static_cast<Ui::SingleMediaPreview*>(preview);
  214         return media->deleteRequests() | rpl::map([from] { return from; });
  215     } else {
  216         const auto single = static_cast<Ui::SingleFilePreview*>(preview);
  217         return single->deleteRequests() | rpl::map([from] { return from; });
  218     }
  219 }
  220 
  221 rpl::producer<int> SendFilesBox::Block::itemReplaceRequest() const {
  222     using namespace rpl::mappers;
  223 
  224     const auto preview = _preview.get();
  225     const auto from = _from;
  226     if (_isAlbum) {
  227         const auto album = static_cast<Ui::AlbumPreview*>(preview);
  228         return album->thumbChanged() | rpl::map(_1 + from);
  229     } else if (_isSingleMedia) {
  230         const auto media = static_cast<Ui::SingleMediaPreview*>(preview);
  231         return media->editRequests() | rpl::map([from] { return from; });
  232     } else {
  233         const auto single = static_cast<Ui::SingleFilePreview*>(preview);
  234         return single->editRequests() | rpl::map([from] { return from; });
  235     }
  236 }
  237 
  238 rpl::producer<int> SendFilesBox::Block::itemModifyRequest() const {
  239     using namespace rpl::mappers;
  240 
  241     const auto preview = _preview.get();
  242     const auto from = _from;
  243     if (_isAlbum) {
  244         const auto album = static_cast<Ui::AlbumPreview*>(preview);
  245         return album->thumbModified() | rpl::map(_1 + from);
  246     } else if (_isSingleMedia) {
  247         const auto media = static_cast<Ui::SingleMediaPreview*>(preview);
  248         return media->modifyRequests() | rpl::map_to(from);
  249     } else {
  250         return rpl::never<int>();
  251     }
  252 }
  253 
  254 void SendFilesBox::Block::setSendWay(Ui::SendFilesWay way) {
  255     if (!_isAlbum) {
  256         if (_isSingleMedia) {
  257             const auto media = static_cast<Ui::SingleMediaPreview*>(
  258                 _preview.get());
  259             media->setSendWay(way);
  260         }
  261         return;
  262     }
  263     applyChanges();
  264     const auto album = static_cast<Ui::AlbumPreview*>(_preview.get());
  265     album->setSendWay(way);
  266 }
  267 
  268 void SendFilesBox::Block::toggleSpoilers(bool enabled) {
  269     if (_isAlbum) {
  270         const auto album = static_cast<Ui::AlbumPreview*>(_preview.get());
  271         album->toggleSpoilers(enabled);
  272     } else if (_isSingleMedia) {
  273         const auto media = static_cast<Ui::SingleMediaPreview*>(
  274             _preview.get());
  275         media->setSpoiler(enabled);
  276     }
  277 }
  278 
  279 void SendFilesBox::Block::applyChanges() {
  280     if (!_isAlbum) {
  281         if (_isSingleMedia) {
  282             const auto media = static_cast<Ui::SingleMediaPreview*>(
  283                 _preview.get());
  284             if (media->canHaveSpoiler()) {
  285                 (*_items)[_from].spoiler = media->hasSpoiler();
  286             }
  287         }
  288         return;
  289     }
  290     const auto album = static_cast<Ui::AlbumPreview*>(_preview.get());
  291     const auto order = album->takeOrder();
  292     const auto guard = gsl::finally([&] {
  293         const auto spoilered = album->collectSpoileredIndices();
  294         for (auto i = 0, count = int(order.size()); i != count; ++i) {
  295             if (album->canHaveSpoiler(i)) {
  296                 (*_items)[_from + i].spoiler = spoilered.contains(i);
  297             }
  298         }
  299     });
  300     const auto isIdentity = [&] {
  301         for (auto i = 0, count = int(order.size()); i != count; ++i) {
  302             if (order[i] != i) {
  303                 return false;
  304             }
  305         }
  306         return true;
  307     }();
  308     if (isIdentity) {
  309         return;
  310     }
  311 
  312     auto elements = std::vector<Ui::PreparedFile>();
  313     elements.reserve(order.size());
  314     for (const auto index : order) {
  315         elements.push_back(std::move((*_items)[_from + index]));
  316     }
  317     for (auto i = 0, count = int(order.size()); i != count; ++i) {
  318         (*_items)[_from + i] = std::move(elements[i]);
  319     }
  320 }
  321 
  322 SendFilesBox::SendFilesBox(
  323     QWidget*,
  324     not_null<Window::SessionController*> controller,
  325     Ui::PreparedList &&list,
  326     const TextWithTags &caption,
  327     SendFilesLimits limits,
  328     SendFilesCheck check,
  329     Api::SendType sendType,
  330     SendMenu::Type sendMenuType)
  331 : _controller(controller)
  332 , _sendType(sendType)
  333 , _titleHeight(st::boxTitleHeight)
  334 , _list(std::move(list))
  335 , _limits(limits)
  336 , _sendMenuType(sendMenuType)
  337 , _check(std::move(check))
  338 , _caption(this, st::confirmCaptionArea, Ui::InputField::Mode::MultiLine)
  339 , _prefilledCaptionText(std::move(caption))
  340 , _scroll(this, st::boxScroll)
  341 , _inner(
  342     _scroll->setOwnedWidget(
  343         object_ptr<Ui::VerticalLayout>(_scroll.data()))) {
  344     enqueueNextPrepare();
  345 }
  346 
  347 void SendFilesBox::initPreview() {
  348     using namespace rpl::mappers;
  349 
  350     refreshControls(true);
  351 
  352     updateBoxSize();
  353 
  354     _dimensionsLifetime.destroy();
  355     _inner->resizeToWidth(st::boxWideWidth);
  356 
  357     rpl::combine(
  358         _inner->heightValue(),
  359         _footerHeight.value(),
  360         _titleHeight.value(),
  361         _1 + _2 + _3
  362     ) | rpl::start_with_next([=](int height) {
  363         setDimensions(
  364             st::boxWideWidth,
  365             std::min(st::sendMediaPreviewHeightMax, height),
  366             true);
  367     }, _dimensionsLifetime);
  368 }
  369 
  370 void SendFilesBox::enqueueNextPrepare() {
  371     if (_preparing) {
  372         return;
  373     }
  374     while (!_list.filesToProcess.empty()
  375         && _list.filesToProcess.front().information) {
  376         auto file = std::move(_list.filesToProcess.front());
  377         _list.filesToProcess.pop_front();
  378         addFile(std::move(file));
  379     }
  380     if (_list.filesToProcess.empty()) {
  381         return;
  382     }
  383     auto file = std::move(_list.filesToProcess.front());
  384     _list.filesToProcess.pop_front();
  385     const auto weak = Ui::MakeWeak(this);
  386     _preparing = true;
  387     const auto sideLimit = PhotoSideLimit(); // Get on main thread.
  388     crl::async([weak, sideLimit, file = std::move(file)]() mutable {
  389         Storage::PrepareDetails(file, st::sendMediaPreviewSize, sideLimit);
  390         crl::on_main([weak, file = std::move(file)]() mutable {
  391             if (weak) {
  392                 weak->addPreparedAsyncFile(std::move(file));
  393             }
  394         });
  395     });
  396 }
  397 
  398 void SendFilesBox::prepare() {
  399     initSendWay();
  400     setupCaption();
  401     setupSendWayControls();
  402     preparePreview();
  403     initPreview();
  404     SetupShadowsToScrollContent(this, _scroll, _inner->heightValue());
  405 
  406     boxClosing() | rpl::start_with_next([=] {
  407         if (!_confirmed && _cancelledCallback) {
  408             _cancelledCallback();
  409         }
  410     }, lifetime());
  411 
  412     setupDragArea();
  413 }
  414 
  415 void SendFilesBox::setupDragArea() {
  416     // Avoid both drag areas appearing at one time.
  417     auto computeState = [=](const QMimeData *data) {
  418         using DragState = Storage::MimeDataState;
  419         const auto state = Storage::ComputeMimeDataState(data);
  420         return (state == DragState::PhotoFiles)
  421             ? DragState::Image
  422             : state;
  423     };
  424     const auto areas = DragArea::SetupDragAreaToContainer(
  425         this,
  426         [=](not_null<const QMimeData*> d) { return canAddFiles(d); },
  427         [=](bool f) { _caption->setAcceptDrops(f); },
  428         [=] { updateControlsGeometry(); },
  429         std::move(computeState));
  430 
  431     const auto droppedCallback = [=](bool compress) {
  432         return [=](const QMimeData *data) {
  433             addFiles(data);
  434             Window::ActivateWindow(_controller);
  435         };
  436     };
  437     areas.document->setDroppedCallback(droppedCallback(false));
  438     areas.photo->setDroppedCallback(droppedCallback(true));
  439 }
  440 
  441 void SendFilesBox::refreshAllAfterChanges(int fromItem, Fn<void()> perform) {
  442     auto fromBlock = 0;
  443     for (auto count = int(_blocks.size()); fromBlock != count; ++fromBlock) {
  444         if (_blocks[fromBlock].tillIndex() >= fromItem) {
  445             break;
  446         }
  447     }
  448     for (auto index = fromBlock; index < _blocks.size(); ++index) {
  449         _blocks[index].applyChanges();
  450     }
  451     if (perform) {
  452         perform();
  453     }
  454     generatePreviewFrom(fromBlock);
  455     {
  456         auto sendWay = _sendWay.current();
  457         sendWay.setHasCompressedStickers(_list.hasSticker());
  458         if (_limits & SendFilesAllow::OnlyOne) {
  459             if (_list.files.size() > 1) {
  460                 sendWay.setGroupFiles(true);
  461             }
  462         }
  463         _sendWay = sendWay;
  464     }
  465     _inner->resizeToWidth(st::boxWideWidth);
  466     refreshControls();
  467     captionResized();
  468 }
  469 
  470 void SendFilesBox::openDialogToAddFileToAlbum() {
  471     const auto toastParent = Ui::BoxShow(this).toastParent();
  472     const auto checkResult = [=](const Ui::PreparedList &list) {
  473         if (!(_limits & SendFilesAllow::OnlyOne)) {
  474             return true;
  475         } else if (!_list.canBeSentInSlowmodeWith(list)) {
  476             Ui::Toast::Show(toastParent, tr::lng_slowmode_no_many(tr::now));
  477             return false;
  478         }
  479         return true;
  480     };
  481     const auto callback = [=](FileDialog::OpenResult &&result) {
  482         const auto premium = _controller->session().premium();
  483         FileDialogCallback(
  484             std::move(result),
  485             checkResult,
  486             [=](Ui::PreparedList list) { addFiles(std::move(list)); },
  487             premium,
  488             toastParent);
  489     };
  490 
  491     FileDialog::GetOpenPaths(
  492         this,
  493         tr::lng_choose_file(tr::now),
  494         FileDialog::AllOrImagesFilter(),
  495         crl::guard(this, callback));
  496 }
  497 
  498 void SendFilesBox::refreshButtons() {
  499     clearButtons();
  500 
  501     _send = addButton(
  502         (_sendType == Api::SendType::Normal
  503             ? tr::lng_send_button()
  504             : tr::lng_create_group_next()),
  505         [=] { send({}); });
  506     if (_sendType == Api::SendType::Normal) {
  507         SendMenu::SetupMenuAndShortcuts(
  508             _send,
  509             [=] { return _sendMenuType; },
  510             [=] { sendSilent(); },
  511             [=] { sendScheduled(); },
  512             [=] { sendWhenOnline(); });
  513     }
  514     addButton(tr::lng_cancel(), [=] { closeBox(); });
  515     _addFile = addLeftButton(
  516         tr::lng_stickers_featured_add(),
  517         base::fn_delayed(st::historyAttach.ripple.hideDuration, this, [=] {
  518             openDialogToAddFileToAlbum();
  519         }));
  520 
  521     addMenuButton();
  522 }
  523 
  524 bool SendFilesBox::hasSendMenu() const {
  525     return (_sendMenuType != SendMenu::Type::Disabled);
  526 }
  527 
  528 bool SendFilesBox::hasSpoilerMenu() const {
  529     const auto allAreVideo = !ranges::any_of(_list.files, [](const auto &f) {
  530         using Type = Ui::PreparedFile::Type;
  531         return (f.type != Type::Video);
  532     });
  533     const auto allAreMedia = !ranges::any_of(_list.files, [](const auto &f) {
  534         using Type = Ui::PreparedFile::Type;
  535         return (f.type != Type::Photo) && (f.type != Type::Video);
  536     });
  537     return allAreVideo
  538         || (allAreMedia && _sendWay.current().sendImagesAsPhotos());
  539 }
  540 
  541 void SendFilesBox::applyBlockChanges() {
  542     for (auto &block : _blocks) {
  543         block.applyChanges();
  544     }
  545 }
  546 
  547 bool SendFilesBox::allWithSpoilers() {
  548     applyBlockChanges();
  549     return ranges::all_of(_list.files, &Ui::PreparedFile::spoiler);
  550 }
  551 
  552 void SendFilesBox::toggleSpoilers(bool enabled) {
  553     for (auto &file : _list.files) {
  554         file.spoiler = enabled;
  555     }
  556     for (auto &block : _blocks) {
  557         block.toggleSpoilers(enabled);
  558     }
  559 }
  560 
  561 void SendFilesBox::addMenuButton() {
  562     if (!hasSendMenu() && !hasSpoilerMenu()) {
  563         return;
  564     }
  565 
  566     const auto top = addTopButton(st::infoTopBarMenu);
  567     top->setClickedCallback([=] {
  568         _menu = base::make_unique_q<Ui::PopupMenu>(
  569             top,
  570             st::popupMenuExpandedSeparator);
  571         if (hasSpoilerMenu()) {
  572             const auto spoilered = allWithSpoilers();
  573             _menu->addAction(
  574                 (spoilered
  575                     ? tr::lng_context_disable_spoiler(tr::now)
  576                     : tr::lng_context_spoiler_effect(tr::now)),
  577                 [=] { toggleSpoilers(!spoilered); },
  578                 spoilered ? &st::menuIconSpoilerOff : &st::menuIconSpoiler);
  579             if (hasSendMenu()) {
  580                 _menu->addSeparator();
  581             }
  582         }
  583         if (hasSendMenu()) {
  584             SendMenu::FillSendMenu(
  585                 _menu.get(),
  586                 _sendMenuType,
  587                 [=] { sendSilent(); },
  588                 [=] { sendScheduled(); },
  589                 [=] { sendWhenOnline(); });
  590         }
  591         _menu->popup(QCursor::pos());
  592         return true;
  593     });
  594 
  595 }
  596 
  597 void SendFilesBox::initSendWay() {
  598     _sendWay = [&] {
  599         auto result = Core::App().settings().sendFilesWay();
  600         result.setHasCompressedStickers(_list.hasSticker());
  601         if ((_limits & SendFilesAllow::OnlyOne)
  602             && (_list.files.size() > 1)) {
  603             result.setGroupFiles(true);
  604         }
  605         if (_list.overrideSendImagesAsPhotos == false) {
  606             if (!(_limits & SendFilesAllow::OnlyOne)
  607                 || !_list.hasSticker()) {
  608                 result.setSendImagesAsPhotos(false);
  609             }
  610             return result;
  611         } else if (_list.overrideSendImagesAsPhotos == true) {
  612             result.setSendImagesAsPhotos(true);
  613             const auto silent = true;
  614             if (!checkWithWay(result, silent)) {
  615                 result.setSendImagesAsPhotos(false);
  616             }
  617             return result;
  618         }
  619         const auto silent = true;
  620         if (!checkWithWay(result, silent)) {
  621             result.setSendImagesAsPhotos(!result.sendImagesAsPhotos());
  622         }
  623         return result;
  624     }();
  625     _sendWay.changes(
  626     ) | rpl::start_with_next([=](SendFilesWay value) {
  627         const auto hidden = [&] {
  628             return !_caption || _caption->isHidden();
  629         };
  630         const auto was = hidden();
  631         updateCaptionPlaceholder();
  632         updateEmojiPanelGeometry();
  633         for (auto &block : _blocks) {
  634             block.setSendWay(value);
  635         }
  636         if (!hasSendMenu()) {
  637             refreshButtons();
  638         }
  639         if (was != hidden()) {
  640             updateBoxSize();
  641             updateControlsGeometry();
  642         }
  643         setInnerFocus();
  644     }, lifetime());
  645 }
  646 
  647 void SendFilesBox::updateCaptionPlaceholder() {
  648     if (!_caption) {
  649         return;
  650     }
  651     const auto way = _sendWay.current();
  652     if (!_list.canAddCaption(
  653             way.groupFiles() && way.sendImagesAsPhotos(),
  654             way.sendImagesAsPhotos())
  655         && ((_limits & SendFilesAllow::OnlyOne)
  656             || !(_limits & SendFilesAllow::Texts))) {
  657         _caption->hide();
  658         if (_emojiToggle) {
  659             _emojiToggle->hide();
  660         }
  661     } else {
  662         _caption->setPlaceholder(FieldPlaceholder(_list, way));
  663         _caption->show();
  664         if (_emojiToggle) {
  665             _emojiToggle->show();
  666         }
  667     }
  668 }
  669 
  670 void SendFilesBox::preparePreview() {
  671     generatePreviewFrom(0);
  672 }
  673 
  674 void SendFilesBox::generatePreviewFrom(int fromBlock) {
  675     Expects(fromBlock <= _blocks.size());
  676 
  677     using Type = Ui::PreparedFile::Type;
  678 
  679     _blocks.erase(_blocks.begin() + fromBlock, _blocks.end());
  680 
  681     const auto fromItem = _blocks.empty() ? 0 : _blocks.back().tillIndex();
  682     Assert(fromItem <= _list.files.size());
  683 
  684     auto albumStart = -1;
  685     for (auto i = fromItem, till = int(_list.files.size()); i != till; ++i) {
  686         const auto type = _list.files[i].type;
  687         if (albumStart >= 0) {
  688             const auto albumCount = (i - albumStart);
  689             if ((type == Type::File)
  690                 || (type == Type::None)
  691                 || (type == Type::Music)
  692                 || (albumCount == Ui::MaxAlbumItems())) {
  693                 pushBlock(std::exchange(albumStart, -1), i);
  694             } else {
  695                 continue;
  696             }
  697         }
  698         if (type != Type::File
  699             && type != Type::Music
  700             && type != Type::None) {
  701             if (albumStart < 0) {
  702                 albumStart = i;
  703             }
  704             continue;
  705         }
  706         pushBlock(i, i + 1);
  707     }
  708     if (albumStart >= 0) {
  709         pushBlock(albumStart, _list.files.size());
  710     }
  711 }
  712 
  713 void SendFilesBox::pushBlock(int from, int till) {
  714     const auto gifPaused = [controller = _controller] {
  715         return controller->isGifPausedAtLeastFor(
  716             Window::GifPauseReason::Layer);
  717     };
  718     _blocks.emplace_back(
  719         _inner.data(),
  720         &_list.files,
  721         from,
  722         till,
  723         gifPaused,
  724         _sendWay.current());
  725     auto &block = _blocks.back();
  726     const auto widget = _inner->add(
  727         block.takeWidget(),
  728         QMargins(0, _inner->count() ? st::sendMediaRowSkip : 0, 0, 0));
  729 
  730     block.itemDeleteRequest(
  731     ) | rpl::filter([=] {
  732         return !_removingIndex;
  733     }) | rpl::start_with_next([=](int index) {
  734         _removingIndex = index;
  735         crl::on_main(this, [=] {
  736             const auto index = base::take(_removingIndex).value_or(-1);
  737             if (index < 0 || index >= _list.files.size()) {
  738                 return;
  739             }
  740             // Just close the box if it is the only one.
  741             if (_list.files.size() == 1) {
  742                 closeBox();
  743                 return;
  744             }
  745             refreshAllAfterChanges(index, [&] {
  746                 _list.files.erase(_list.files.begin() + index);
  747             });
  748         });
  749     }, widget->lifetime());
  750 
  751     const auto toastParent = Ui::BoxShow(this).toastParent();
  752     block.itemReplaceRequest(
  753     ) | rpl::start_with_next([=](int index) {
  754         const auto replace = [=](Ui::PreparedList list) {
  755             if (list.files.empty()) {
  756                 return;
  757             }
  758             refreshAllAfterChanges(from, [&] {
  759                 _list.files[index] = std::move(list.files.front());
  760             });
  761         };
  762         const auto checkSlowmode = [=](const Ui::PreparedList &list) {
  763             if (list.files.empty() || !(_limits & SendFilesAllow::OnlyOne)) {
  764                 return true;
  765             }
  766             auto removing = std::move(_list.files[index]);
  767             std::swap(_list.files[index], _list.files.back());
  768             _list.files.pop_back();
  769             const auto result = _list.canBeSentInSlowmodeWith(list);
  770             _list.files.push_back(std::move(removing));
  771             std::swap(_list.files[index], _list.files.back());
  772             if (!result) {
  773                 Ui::Toast::Show(
  774                     toastParent,
  775                     tr::lng_slowmode_no_many(tr::now));
  776                 return false;
  777             }
  778             return true;
  779         };
  780         const auto checkRights = [=](const Ui::PreparedList &list) {
  781             if (list.files.empty()) {
  782                 return true;
  783             }
  784             auto removing = std::move(_list.files[index]);
  785             std::swap(_list.files[index], _list.files.back());
  786             _list.files.pop_back();
  787             auto way = _sendWay.current();
  788             const auto has = _list.hasSticker()
  789                 || list.files.front().isSticker();
  790             way.setHasCompressedStickers(has);
  791             if (_limits & SendFilesAllow::OnlyOne) {
  792                 way.setGroupFiles(true);
  793             }
  794             const auto silent = true;
  795             if (!checkWith(list, way, silent)
  796                 && (!(_limits & SendFilesAllow::OnlyOne) || !has)) {
  797                 way.setSendImagesAsPhotos(!way.sendImagesAsPhotos());
  798             }
  799             const auto result = checkWith(list, way);
  800             _list.files.push_back(std::move(removing));
  801             std::swap(_list.files[index], _list.files.back());
  802             if (!result) {
  803                 return false;
  804             }
  805             _sendWay = way;
  806             return true;
  807         };
  808         const auto checkResult = [=](const Ui::PreparedList &list) {
  809             return checkSlowmode(list) && checkRights(list);
  810         };
  811         const auto callback = [=](FileDialog::OpenResult &&result) {
  812             const auto premium = _controller->session().premium();
  813             FileDialogCallback(
  814                 std::move(result),
  815                 checkResult,
  816                 replace,
  817                 premium,
  818                 toastParent);
  819         };
  820 
  821         FileDialog::GetOpenPath(
  822             this,
  823             tr::lng_choose_file(tr::now),
  824             FileDialog::AllOrImagesFilter(),
  825             crl::guard(this, callback));
  826     }, widget->lifetime());
  827 
  828     const auto openedOnce = widget->lifetime().make_state<bool>(false);
  829     block.itemModifyRequest(
  830     ) | rpl::start_with_next([=, controller = _controller](int index) {
  831         if (!(*openedOnce)) {
  832             controller->session().settings().incrementPhotoEditorHintShown();
  833             controller->session().saveSettings();
  834         }
  835         *openedOnce = true;
  836         Editor::OpenWithPreparedFile(
  837             this,
  838             controller,
  839             &_list.files[index],
  840             st::sendMediaPreviewSize,
  841             [=] { refreshAllAfterChanges(from); });
  842     }, widget->lifetime());
  843 }
  844 
  845 void SendFilesBox::refreshControls(bool initial) {
  846     if (initial || !hasSendMenu()) {
  847         refreshButtons();
  848     }
  849     refreshTitleText();
  850     updateSendWayControls();
  851     updateCaptionPlaceholder();
  852 }
  853 
  854 void SendFilesBox::setupSendWayControls() {
  855     const auto groupFilesFirst = _sendWay.current().groupFiles();
  856     const auto asPhotosFirst = _sendWay.current().sendImagesAsPhotos();
  857     _groupFiles.create(
  858         this,
  859         tr::lng_send_grouped(tr::now),
  860         groupFilesFirst,
  861         st::defaultBoxCheckbox);
  862     _sendImagesAsPhotos.create(
  863         this,
  864         tr::lng_send_compressed(tr::now),
  865         _sendWay.current().sendImagesAsPhotos(),
  866         st::defaultBoxCheckbox);
  867 
  868     _sendWay.changes(
  869     ) | rpl::start_with_next([=](SendFilesWay value) {
  870         _groupFiles->setChecked(value.groupFiles());
  871         _sendImagesAsPhotos->setChecked(value.sendImagesAsPhotos());
  872     }, lifetime());
  873 
  874     _groupFiles->checkedChanges(
  875     ) | rpl::start_with_next([=](bool checked) {
  876         auto sendWay = _sendWay.current();
  877         if (sendWay.groupFiles() == checked) {
  878             return;
  879         }
  880         sendWay.setGroupFiles(checked);
  881         if (checkWithWay(sendWay)) {
  882             _sendWay = sendWay;
  883         } else {
  884             Ui::PostponeCall(_groupFiles.data(), [=] {
  885                 _groupFiles->setChecked(!checked);
  886             });
  887         }
  888     }, lifetime());
  889 
  890     _sendImagesAsPhotos->checkedChanges(
  891     ) | rpl::start_with_next([=](bool checked) {
  892         auto sendWay = _sendWay.current();
  893         if (sendWay.sendImagesAsPhotos() == checked) {
  894             return;
  895         }
  896         sendWay.setSendImagesAsPhotos(checked);
  897         if (checkWithWay(sendWay)) {
  898             _sendWay = sendWay;
  899         } else {
  900             Ui::PostponeCall(_sendImagesAsPhotos.data(), [=] {
  901                 _sendImagesAsPhotos->setChecked(!checked);
  902             });
  903         }
  904     }, lifetime());
  905 
  906     _wayRemember.create(
  907         this,
  908         tr::lng_remember(tr::now),
  909         false,
  910         st::defaultBoxCheckbox);
  911     _wayRemember->hide();
  912     rpl::combine(
  913         _groupFiles->checkedValue(),
  914         _sendImagesAsPhotos->checkedValue()
  915     ) | rpl::start_with_next([=](bool groupFiles, bool asPhoto) {
  916         _wayRemember->setVisible(
  917             (groupFiles != groupFilesFirst) || (asPhoto != asPhotosFirst));
  918         captionResized();
  919     }, lifetime());
  920 
  921     _hintLabel.create(
  922         this,
  923         tr::lng_edit_photo_editor_hint(tr::now),
  924         st::editMediaHintLabel);
  925 }
  926 
  927 bool SendFilesBox::checkWithWay(Ui::SendFilesWay way, bool silent) const {
  928     return checkWith({}, way, silent);
  929 }
  930 
  931 bool SendFilesBox::checkWith(
  932         const Ui::PreparedList &added,
  933         Ui::SendFilesWay way,
  934         bool silent) const {
  935     if (!_check) {
  936         return true;
  937     }
  938     const auto compress = way.sendImagesAsPhotos();
  939     auto &already = _list.files;
  940     for (const auto &file : ranges::views::concat(already, added.files)) {
  941         if (!_check(file, compress, silent)) {
  942             return false;
  943         }
  944     }
  945     return true;
  946 }
  947 
  948 void SendFilesBox::updateSendWayControls() {
  949     const auto onlyOne = (_limits & SendFilesAllow::OnlyOne);
  950     _groupFiles->setVisible(_list.hasGroupOption(onlyOne));
  951     _sendImagesAsPhotos->setVisible(
  952         _list.hasSendImagesAsPhotosOption(onlyOne));
  953     _sendImagesAsPhotos->setText((_list.files.size() > 1)
  954         ? tr::lng_send_compressed(tr::now)
  955         : tr::lng_send_compressed_one(tr::now));
  956 
  957     _hintLabel->setVisible(
  958         _controller->session().settings().photoEditorHintShown()
  959             ? _list.canHaveEditorHintLabel()
  960             : false);
  961 }
  962 
  963 void SendFilesBox::setupCaption() {
  964     const auto allow = [=](const auto&) {
  965         return (_limits & SendFilesAllow::EmojiWithoutPremium);
  966     };
  967     InitMessageFieldHandlers(
  968         _controller,
  969         _caption.data(),
  970         Window::GifPauseReason::Layer,
  971         allow);
  972     Ui::Emoji::SuggestionsController::Init(
  973         getDelegate()->outerContainer(),
  974         _caption,
  975         &_controller->session(),
  976         { .suggestCustomEmoji = true, .allowCustomWithoutPremium = allow });
  977 
  978     if (!_prefilledCaptionText.text.isEmpty()) {
  979         _caption->setTextWithTags(
  980             _prefilledCaptionText,
  981             Ui::InputField::HistoryAction::Clear);
  982 
  983         auto cursor = _caption->textCursor();
  984         cursor.movePosition(QTextCursor::End);
  985         _caption->setTextCursor(cursor);
  986     }
  987     _caption->setSubmitSettings(
  988         Core::App().settings().sendSubmitWay());
  989     _caption->setMaxLength(kMaxMessageLength);
  990 
  991     connect(_caption, &Ui::InputField::resized, [=] {
  992         captionResized();
  993     });
  994     connect(_caption, &Ui::InputField::submitted, [=](
  995             Qt::KeyboardModifiers modifiers) {
  996         const auto ctrlShiftEnter = modifiers.testFlag(Qt::ShiftModifier)
  997             && (modifiers.testFlag(Qt::ControlModifier)
  998                 || modifiers.testFlag(Qt::MetaModifier));
  999         send({}, ctrlShiftEnter);
 1000     });
 1001     connect(_caption, &Ui::InputField::cancelled, [=] { closeBox(); });
 1002     _caption->setMimeDataHook([=](
 1003             not_null<const QMimeData*> data,
 1004             Ui::InputField::MimeAction action) {
 1005         if (action == Ui::InputField::MimeAction::Check) {
 1006             return canAddFiles(data);
 1007         } else if (action == Ui::InputField::MimeAction::Insert) {
 1008             return addFiles(data);
 1009         }
 1010         Unexpected("action in MimeData hook.");
 1011     });
 1012 
 1013     updateCaptionPlaceholder();
 1014     setupEmojiPanel();
 1015 }
 1016 
 1017 void SendFilesBox::setupEmojiPanel() {
 1018     Expects(_caption != nullptr);
 1019 
 1020     const auto container = getDelegate()->outerContainer();
 1021     using Selector = ChatHelpers::TabbedSelector;
 1022     _emojiPanel = base::make_unique_q<ChatHelpers::TabbedPanel>(
 1023         container,
 1024         _controller,
 1025         object_ptr<Selector>(
 1026             nullptr,
 1027             _controller,
 1028             Window::GifPauseReason::Layer,
 1029             Selector::Mode::EmojiOnly));
 1030     _emojiPanel->setDesiredHeightValues(
 1031         1.,
 1032         st::emojiPanMinHeight / 2,
 1033         st::emojiPanMinHeight);
 1034     _emojiPanel->hide();
 1035     _emojiPanel->selector()->setAllowEmojiWithoutPremium(
 1036         _limits & SendFilesAllow::EmojiWithoutPremium);
 1037     _emojiPanel->selector()->emojiChosen(
 1038     ) | rpl::start_with_next([=](ChatHelpers::EmojiChosen data) {
 1039         Ui::InsertEmojiAtCursor(_caption->textCursor(), data.emoji);
 1040     }, lifetime());
 1041     _emojiPanel->selector()->customEmojiChosen(
 1042     ) | rpl::start_with_next([=](ChatHelpers::FileChosen data) {
 1043         const auto info = data.document->sticker();
 1044         if (info
 1045             && info->setType == Data::StickersType::Emoji
 1046             && !_controller->session().premium()
 1047             && !(_limits & SendFilesAllow::EmojiWithoutPremium)) {
 1048             ShowPremiumPreviewBox(
 1049                 _controller,
 1050                 PremiumPreview::AnimatedEmoji);
 1051         } else {
 1052             Data::InsertCustomEmoji(_caption.data(), data.document);
 1053         }
 1054     }, lifetime());
 1055 
 1056     const auto filterCallback = [=](not_null<QEvent*> event) {
 1057         emojiFilterForGeometry(event);
 1058         return base::EventFilterResult::Continue;
 1059     };
 1060     _emojiFilter.reset(base::install_event_filter(container, filterCallback));
 1061 
 1062     _emojiToggle.create(this, st::boxAttachEmoji);
 1063     _emojiToggle->setVisible(!_caption->isHidden());
 1064     _emojiToggle->installEventFilter(_emojiPanel);
 1065     _emojiToggle->addClickHandler([=] {
 1066         _emojiPanel->toggleAnimated();
 1067     });
 1068 }
 1069 
 1070 void SendFilesBox::emojiFilterForGeometry(not_null<QEvent*> event) {
 1071     const auto type = event->type();
 1072     if (type == QEvent::Move || type == QEvent::Resize) {
 1073         // updateEmojiPanelGeometry uses not only container geometry, but
 1074         // also container children geometries that will be updated later.
 1075         crl::on_main(this, [=] { updateEmojiPanelGeometry(); });
 1076     }
 1077 }
 1078 
 1079 void SendFilesBox::updateEmojiPanelGeometry() {
 1080     const auto parent = _emojiPanel->parentWidget();
 1081     const auto global = _emojiToggle->mapToGlobal({ 0, 0 });
 1082     const auto local = parent->mapFromGlobal(global);
 1083     _emojiPanel->moveBottomRight(
 1084         local.y(),
 1085         local.x() + _emojiToggle->width() * 3);
 1086 }
 1087 
 1088 void SendFilesBox::captionResized() {
 1089     updateBoxSize();
 1090     updateControlsGeometry();
 1091     updateEmojiPanelGeometry();
 1092     update();
 1093 }
 1094 
 1095 bool SendFilesBox::canAddFiles(not_null<const QMimeData*> data) const {
 1096     return data->hasImage() || CanAddUrls(Core::ReadMimeUrls(data));
 1097 }
 1098 
 1099 bool SendFilesBox::addFiles(not_null<const QMimeData*> data) {
 1100     const auto premium = _controller->session().premium();
 1101     auto list = [&] {
 1102         const auto urls = Core::ReadMimeUrls(data);
 1103         auto result = CanAddUrls(urls)
 1104             ? Storage::PrepareMediaList(
 1105                 urls,
 1106                 st::sendMediaPreviewSize,
 1107                 premium)
 1108             : Ui::PreparedList(
 1109                 Ui::PreparedList::Error::EmptyFile,
 1110                 QString());
 1111         if (result.error == Ui::PreparedList::Error::None) {
 1112             return result;
 1113         } else if (auto read = Core::ReadMimeImage(data)) {
 1114             return Storage::PrepareMediaFromImage(
 1115                 std::move(read.image),
 1116                 std::move(read.content),
 1117                 st::sendMediaPreviewSize);
 1118         }
 1119         return result;
 1120     }();
 1121     return addFiles(std::move(list));
 1122 }
 1123 
 1124 bool SendFilesBox::addFiles(Ui::PreparedList list) {
 1125     if (list.error != Ui::PreparedList::Error::None) {
 1126         return false;
 1127     }
 1128     const auto count = int(_list.files.size());
 1129     _list.filesToProcess.insert(
 1130         _list.filesToProcess.end(),
 1131         std::make_move_iterator(list.files.begin()),
 1132         std::make_move_iterator(list.files.end()));
 1133     _list.filesToProcess.insert(
 1134         _list.filesToProcess.end(),
 1135         std::make_move_iterator(list.filesToProcess.begin()),
 1136         std::make_move_iterator(list.filesToProcess.end()));
 1137     enqueueNextPrepare();
 1138     if (_list.files.size() > count) {
 1139         refreshAllAfterChanges(count);
 1140     }
 1141     return true;
 1142 }
 1143 
 1144 void SendFilesBox::addPreparedAsyncFile(Ui::PreparedFile &&file) {
 1145     Expects(file.information != nullptr);
 1146 
 1147     _preparing = false;
 1148     const auto count = int(_list.files.size());
 1149     addFile(std::move(file));
 1150     enqueueNextPrepare();
 1151     if (_list.files.size() > count) {
 1152         refreshAllAfterChanges(count);
 1153     }
 1154     if (!_preparing && _whenReadySend) {
 1155         _whenReadySend();
 1156     }
 1157 }
 1158 
 1159 void SendFilesBox::addFile(Ui::PreparedFile &&file) {
 1160     // canBeSentInSlowmode checks for non empty filesToProcess.
 1161     auto saved = base::take(_list.filesToProcess);
 1162     _list.files.push_back(std::move(file));
 1163     const auto lastOk = [&] {
 1164         auto way = _sendWay.current();
 1165         if (_limits & SendFilesAllow::OnlyOne) {
 1166             way.setGroupFiles(true);
 1167             if (!_list.canBeSentInSlowmode()) {
 1168                 return false;
 1169             }
 1170         } else if (!checkWithWay(way)) {
 1171             return false;
 1172         }
 1173         _sendWay = way;
 1174         return true;
 1175     }();
 1176     if (!lastOk) {
 1177         _list.files.pop_back();
 1178     }
 1179     _list.filesToProcess = std::move(saved);
 1180 }
 1181 
 1182 void SendFilesBox::refreshTitleText() {
 1183     using Type = Ui::PreparedFile::Type;
 1184     const auto count = int(_list.files.size());
 1185     if (count > 1) {
 1186         const auto imagesCount = ranges::count(
 1187             _list.files,
 1188             Type::Photo,
 1189             &Ui::PreparedFile::type);
 1190         _titleText = (imagesCount == count)
 1191             ? tr::lng_send_images_selected(tr::now, lt_count, count)
 1192             : tr::lng_send_files_selected(tr::now, lt_count, count);
 1193     } else {
 1194         const auto type = _list.files.empty()
 1195             ? Type::None
 1196             : _list.files.front().type;
 1197         _titleText = (type == Type::Photo)
 1198             ? tr::lng_send_image(tr::now)
 1199             : (type == Type::Video)
 1200             ? tr::lng_send_video(tr::now)
 1201             : tr::lng_send_file(tr::now);
 1202     }
 1203     _titleHeight = st::boxTitleHeight;
 1204 }
 1205 
 1206 void SendFilesBox::updateBoxSize() {
 1207     auto footerHeight = 0;
 1208     if (_caption && !_caption->isHidden()) {
 1209         footerHeight += st::boxPhotoCaptionSkip + _caption->height();
 1210     }
 1211     const auto pairs = std::array<std::pair<RpWidget*, int>, 4>{ {
 1212         { _groupFiles.data(), st::boxPhotoCompressedSkip },
 1213         { _sendImagesAsPhotos.data(), st::boxPhotoCompressedSkip },
 1214         { _wayRemember.data(), st::boxPhotoCompressedSkip },
 1215         { _hintLabel.data(), st::editMediaLabelMargins.top() },
 1216     } };
 1217     for (const auto &pair : pairs) {
 1218         const auto pointer = pair.first;
 1219         if (pointer && !pointer->isHidden()) {
 1220             footerHeight += pair.second + pointer->heightNoMargins();
 1221         }
 1222     }
 1223     _footerHeight = footerHeight;
 1224 }
 1225 
 1226 void SendFilesBox::keyPressEvent(QKeyEvent *e) {
 1227     if (e->matches(QKeySequence::Open)) {
 1228         openDialogToAddFileToAlbum();
 1229     } else if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) {
 1230         const auto modifiers = e->modifiers();
 1231         const auto ctrl = modifiers.testFlag(Qt::ControlModifier)
 1232             || modifiers.testFlag(Qt::MetaModifier);
 1233         const auto shift = modifiers.testFlag(Qt::ShiftModifier);
 1234         send({}, ctrl && shift);
 1235     } else {
 1236         BoxContent::keyPressEvent(e);
 1237     }
 1238 }
 1239 
 1240 void SendFilesBox::paintEvent(QPaintEvent *e) {
 1241     BoxContent::paintEvent(e);
 1242 
 1243     if (!_titleText.isEmpty()) {
 1244         Painter p(this);
 1245 
 1246         p.setFont(st::boxTitleFont);
 1247         p.setPen(st::boxTitleFg);
 1248         p.drawTextLeft(
 1249             st::boxPhotoTitlePosition.x(),
 1250             st::boxTitlePosition.y() - st::boxTopMargin,
 1251             width(),
 1252             _titleText);
 1253     }
 1254 }
 1255 
 1256 void SendFilesBox::resizeEvent(QResizeEvent *e) {
 1257     BoxContent::resizeEvent(e);
 1258     updateControlsGeometry();
 1259 }
 1260 
 1261 void SendFilesBox::updateControlsGeometry() {
 1262     auto bottom = height();
 1263     if (_caption && !_caption->isHidden()) {
 1264         _caption->resize(st::sendMediaPreviewSize, _caption->height());
 1265         _caption->moveToLeft(
 1266             st::boxPhotoPadding.left(),
 1267             bottom - _caption->height());
 1268         bottom -= st::boxPhotoCaptionSkip + _caption->height();
 1269 
 1270         if (_emojiToggle) {
 1271             _emojiToggle->moveToLeft(
 1272                 (st::boxPhotoPadding.left()
 1273                     + st::sendMediaPreviewSize
 1274                     - _emojiToggle->width()),
 1275                 _caption->y() + st::boxAttachEmojiTop);
 1276             _emojiToggle->update();
 1277         }
 1278     }
 1279     const auto pairs = std::array<std::pair<RpWidget*, int>, 4>{ {
 1280         { _hintLabel.data(), st::editMediaLabelMargins.top() },
 1281         { _groupFiles.data(), st::boxPhotoCompressedSkip },
 1282         { _sendImagesAsPhotos.data(), st::boxPhotoCompressedSkip },
 1283         { _wayRemember.data(), st::boxPhotoCompressedSkip },
 1284     } };
 1285     for (const auto &pair : ranges::views::reverse(pairs)) {
 1286         const auto pointer = pair.first;
 1287         if (pointer && !pointer->isHidden()) {
 1288             pointer->moveToLeft(
 1289                 st::boxPhotoPadding.left(),
 1290                 bottom - pointer->heightNoMargins());
 1291             bottom -= pair.second + pointer->heightNoMargins();
 1292         }
 1293     }
 1294     _scroll->resize(width(), bottom - _titleHeight.current());
 1295     _scroll->move(0, _titleHeight.current());
 1296 }
 1297 
 1298 void SendFilesBox::setInnerFocus() {
 1299     if (_caption && !_caption->isHidden()) {
 1300         _caption->setFocusFast();
 1301     } else {
 1302         BoxContent::setInnerFocus();
 1303     }
 1304 }
 1305 
 1306 void SendFilesBox::saveSendWaySettings() {
 1307     auto way = _sendWay.current();
 1308     auto oldWay = Core::App().settings().sendFilesWay();
 1309     if (_groupFiles->isHidden()) {
 1310         way.setGroupFiles(oldWay.groupFiles());
 1311     }
 1312     if (_list.overrideSendImagesAsPhotos == way.sendImagesAsPhotos()
 1313         || _sendImagesAsPhotos->isHidden()) {
 1314         way.setSendImagesAsPhotos(oldWay.sendImagesAsPhotos());
 1315     }
 1316     if (way != oldWay) {
 1317         Core::App().settings().setSendFilesWay(way);
 1318         Core::App().saveSettingsDelayed();
 1319     }
 1320 }
 1321 
 1322 bool SendFilesBox::validateLength(const QString &text) const {
 1323     const auto session = &_controller->session();
 1324     const auto limit = Data::PremiumLimits(session).captionLengthCurrent();
 1325     const auto remove = int(text.size()) - limit;
 1326     const auto way = _sendWay.current();
 1327     if (remove <= 0
 1328         || !_list.canAddCaption(
 1329             way.groupFiles() && way.sendImagesAsPhotos(),
 1330             way.sendImagesAsPhotos())) {
 1331         return true;
 1332     }
 1333     _controller->show(Box(CaptionLimitReachedBox, session, remove));
 1334     return false;
 1335 }
 1336 
 1337 void SendFilesBox::send(
 1338         Api::SendOptions options,
 1339         bool ctrlShiftEnter) {
 1340     if ((_sendType == Api::SendType::Scheduled
 1341         || _sendType == Api::SendType::ScheduledToUser)
 1342         && !options.scheduled) {
 1343         return sendScheduled();
 1344     }
 1345     if (_preparing) {
 1346         _whenReadySend = [=] {
 1347             send(options, ctrlShiftEnter);
 1348         };
 1349         return;
 1350     }
 1351 
 1352     if (_wayRemember && _wayRemember->checked()) {
 1353         saveSendWaySettings();
 1354     }
 1355 
 1356     for (auto &item : _list.files) {
 1357         item.spoiler = false;
 1358     }
 1359     applyBlockChanges();
 1360 
 1361     Storage::ApplyModifications(_list);
 1362 
 1363     _confirmed = true;
 1364     if (_confirmedCallback) {
 1365         auto caption = (_caption && !_caption->isHidden())
 1366             ? _caption->getTextWithAppliedMarkdown()
 1367             : TextWithTags();
 1368         if (!validateLength(caption.text)) {
 1369             return;
 1370         }
 1371         _confirmedCallback(
 1372             std::move(_list),
 1373             _sendWay.current(),
 1374             std::move(caption),
 1375             options,
 1376             ctrlShiftEnter);
 1377     }
 1378     closeBox();
 1379 }
 1380 
 1381 void SendFilesBox::sendSilent() {
 1382     send({ .silent = true });
 1383 }
 1384 
 1385 void SendFilesBox::sendScheduled() {
 1386     const auto type = (_sendType == Api::SendType::ScheduledToUser)
 1387         ? SendMenu::Type::ScheduledToUser
 1388         : _sendMenuType;
 1389     const auto callback = [=](Api::SendOptions options) { send(options); };
 1390     _controller->show(
 1391         HistoryView::PrepareScheduleBox(this, type, callback),
 1392         Ui::LayerOption::KeepOther);
 1393 }
 1394 
 1395 void SendFilesBox::sendWhenOnline() {
 1396     send(Api::DefaultSendWhenOnlineOptions());
 1397 }
 1398 
 1399 SendFilesBox::~SendFilesBox() = default;