/* 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 "boxes/create_poll_box.h" #include "base/call_delayed.h" #include "base/event_filter.h" #include "base/random.h" #include "base/unique_qptr.h" #include "chat_helpers/emoji_suggestions_widget.h" #include "chat_helpers/message_field.h" #include "chat_helpers/tabbed_panel.h" #include "chat_helpers/tabbed_selector.h" #include "core/application.h" #include "core/core_settings.h" #include "data/data_poll.h" #include "data/data_user.h" #include "data/stickers/data_custom_emoji.h" #include "history/view/history_view_schedule_box.h" #include "lang/lang_keys.h" #include "main/main_session.h" #include "menu/menu_send.h" #include "ui/controls/emoji_button.h" #include "ui/rect.h" #include "ui/text/text_utilities.h" #include "ui/toast/toast.h" #include "ui/vertical_list.h" #include "ui/widgets/buttons.h" #include "ui/widgets/checkbox.h" #include "ui/widgets/fields/input_field.h" #include "ui/widgets/labels.h" #include "ui/widgets/shadow.h" #include "ui/wrap/fade_wrap.h" #include "ui/wrap/slide_wrap.h" #include "ui/wrap/vertical_layout.h" #include "window/window_session_controller.h" #include "styles/style_boxes.h" #include "styles/style_chat_helpers.h" // defaultComposeFiles. #include "styles/style_layers.h" #include "styles/style_settings.h" namespace { constexpr auto kQuestionLimit = 255; constexpr auto kMaxOptionsCount = PollData::kMaxOptions; constexpr auto kOptionLimit = 100; constexpr auto kWarnQuestionLimit = 80; constexpr auto kWarnOptionLimit = 30; constexpr auto kSolutionLimit = 200; constexpr auto kWarnSolutionLimit = 60; constexpr auto kErrorLimit = 99; [[nodiscard]] not_null AddEmojiToggleToField( not_null field, not_null box, not_null controller, not_null emojiPanel, QPoint shift) { const auto emojiToggle = Ui::CreateChild( field->parentWidget(), st::defaultComposeFiles.emoji); const auto fade = Ui::CreateChild( emojiToggle, emojiToggle, 0.5); { const auto fadeTarget = Ui::CreateChild(emojiToggle); fadeTarget->resize(emojiToggle->size()); fadeTarget->paintRequest( ) | rpl::start_with_next([=](const QRect &rect) { auto p = QPainter(fadeTarget); if (fade->animating()) { p.fillRect(fadeTarget->rect(), st::boxBg); } fade->paint(p); }, fadeTarget->lifetime()); rpl::single(false) | rpl::then( field->focusedChanges() ) | rpl::start_with_next([=](bool shown) { if (shown) { fade->fadeIn(st::universalDuration); } else { fade->fadeOut(st::universalDuration); } }, emojiToggle->lifetime()); fade->fadeOut(1); fade->finish(); } const auto outer = box->getDelegate()->outerContainer(); const auto allow = [](not_null) { return true; }; InitMessageFieldHandlers( controller, field, Window::GifPauseReason::Layer, allow); Ui::Emoji::SuggestionsController::Init( outer, field, &controller->session(), Ui::Emoji::SuggestionsController::Options{ .suggestCustomEmoji = true, .allowCustomWithoutPremium = allow, }); const auto updateEmojiPanelGeometry = [=] { const auto parent = emojiPanel->parentWidget(); const auto global = emojiToggle->mapToGlobal({ 0, 0 }); const auto local = parent->mapFromGlobal(global); const auto right = local.x() + emojiToggle->width() * 3; const auto isDropDown = local.y() < parent->height() / 2; emojiPanel->setDropDown(isDropDown); if (isDropDown) { emojiPanel->moveTopRight( local.y() + emojiToggle->height(), right); } else { emojiPanel->moveBottomRight(local.y(), right); } }; rpl::combine( box->sizeValue(), field->geometryValue() ) | rpl::start_with_next([=](QSize outer, QRect inner) { emojiToggle->moveToLeft( rect::right(inner) + shift.x(), inner.y() + shift.y()); emojiToggle->update(); }, emojiToggle->lifetime()); emojiToggle->installEventFilter(emojiPanel); emojiToggle->addClickHandler([=] { updateEmojiPanelGeometry(); emojiPanel->toggleAnimated(); }); const auto filterCallback = [=](not_null event) { if (event->type() == QEvent::Enter) { updateEmojiPanelGeometry(); } return base::EventFilterResult::Continue; }; base::install_event_filter(emojiToggle, filterCallback); return emojiToggle; } class Options { public: Options( not_null box, not_null container, not_null controller, ChatHelpers::TabbedPanel *emojiPanel, bool chooseCorrectEnabled); [[nodiscard]] bool hasOptions() const; [[nodiscard]] bool isValid() const; [[nodiscard]] bool hasCorrect() const; [[nodiscard]] std::vector toPollAnswers() const; void focusFirst(); void enableChooseCorrect(bool enabled); [[nodiscard]] rpl::producer usedCount() const; [[nodiscard]] rpl::producer> scrollToWidget() const; [[nodiscard]] rpl::producer<> backspaceInFront() const; [[nodiscard]] rpl::producer<> tabbed() const; private: class Option { public: Option( not_null outer, not_null container, not_null session, int position, std::shared_ptr group); Option(const Option &other) = delete; Option &operator=(const Option &other) = delete; void toggleRemoveAlways(bool toggled); void enableChooseCorrect( std::shared_ptr group); void show(anim::type animated); void destroy(FnMut done); [[nodiscard]] bool hasShadow() const; void createShadow(); void destroyShadow(); [[nodiscard]] bool isEmpty() const; [[nodiscard]] bool isGood() const; [[nodiscard]] bool isTooLong() const; [[nodiscard]] bool isCorrect() const; [[nodiscard]] bool hasFocus() const; void setFocus() const; void clearValue(); void setPlaceholder() const; void removePlaceholder() const; not_null field() const; [[nodiscard]] PollAnswer toPollAnswer(int index) const; [[nodiscard]] rpl::producer removeClicks() const; private: void createRemove(); void createWarning(); void toggleCorrectSpace(bool visible); void updateFieldGeometry(); base::unique_qptr> _wrap; not_null _content; base::unique_qptr> _correct; Ui::Animations::Simple _correctShown; bool _hasCorrect = false; Ui::InputField *_field = nullptr; base::unique_qptr _shadow; base::unique_qptr _remove; rpl::variable *_removeAlways = nullptr; }; [[nodiscard]] bool full() const; [[nodiscard]] bool correctShadows() const; void fixShadows(); void removeEmptyTail(); void addEmptyOption(); void checkLastOption(); void validateState(); void fixAfterErase(); void destroy(std::unique_ptr